-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: add Notifications #1689
base: main
Are you sure you want to change the base?
feat: add Notifications #1689
Conversation
WalkthroughThis pull request introduces a comprehensive notification system for the Polkadot extension, adding multiple components and utilities to manage and display notifications related to referenda, staking rewards, and received funds. The implementation spans several files across the extension, introducing new hooks, components, and routing configurations to support a robust notification mechanism with user-configurable settings. Changes
Assessment against linked issues
Suggested Labels
Poem
Tip CodeRabbit's docstrings feature is now available as part of our Early Access Program! Simply use the command Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
🧹 Nitpick comments (30)
packages/extension-polkagate/src/partials/AccountMenu.tsx (2)
101-116
: Consider performance impact of backdrop blur effect.The blur effect (
backdropFilter: 'blur(5px)'
) can impact performance on lower-end devices. Consider:
- Using a semi-transparent background without blur for such devices
- Adding a CSS
@supports
check forbackdrop-filter
Example implementation:
/* Add to your styles */ @supports not (backdrop-filter: blur(5px)) { .dialog-backdrop { background-color: rgba(0, 0, 0, 0.5); /* fallback */ } }
120-121
: Use theme colors for box shadow.The box shadow color is hardcoded to white (
rgba(255, 255, 255, 0.25)
). For better theme support, consider using the theme's color system.-sx={{ borderRadius: '10px 10px 0px 0px', boxShadow: '0px 0px 10px rgba(255, 255, 255, 0.25)', height: 'parent.innerHeight' }} +sx={(theme) => ({ + borderRadius: '10px 10px 0px 0px', + boxShadow: `0px 0px 10px ${theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.25)' : 'rgba(0, 0, 0, 0.25)'}`, + height: 'parent.innerHeight' +})}packages/extension-polkagate/src/partials/Menu.tsx (2)
60-65
: Consider handling errors more robustly.
While calling “catch(console.error)” is sufficient for logging errors, consider whether more granular error handling or user-facing error reporting may be beneficial.
143-176
: Remove the unnecessary fragment around a single child.
Static analysis identified lines 162–174 as an unneeded React fragment. You can remove it to simplify the JSX.Proposed diff to remove the redundant fragment:
- <> <Grid item> <Infotip2 text={t('Switch Theme')} > <IconButton sx={{ height: '35px', p: 0, width: '35px' }} > <ThemeChanger color='secondary.light' noBorder /> </IconButton> </Infotip2> </Grid> - </>🧰 Tools
🪛 Biome (1.9.4)
[error] 162-174: Avoid using unnecessary Fragment.
A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment(lint/complexity/noUselessFragments)
packages/extension-polkagate/src/util/workers/utils/newReferendaNotification.js (1)
22-30
: Improve type safety and error handling in getRefStatusThe function could benefit from proper TypeScript typing and explicit error handling.
-const getRefStatus = (/** @type {{ [x: string]: any; }} */ item) => { +const getRefStatus = (item: Referenda): string => { for (const status of REFERENDA_STATUS) { if (item[status]) { return status; } } - return 'ongoing'; + return 'ongoing' as const; };packages/extension-polkagate/src/popup/notification/Notifications.tsx (3)
38-70
: Provide user-friendly guidance when no notifications exist
Currently, the user sees the "Introducing notifications!" message in the "FirstNotification" component when there are no notifications. Consider providing a brief step-by-step guide or a tooltip explaining how notifications are generated or triggered.
72-92
: Use React Fragments to avoid extra DOM elements
In the "NotificationList" map, multiple adjacent elements (NotificationItem and Divider) are wrapped in a <> fragment. To ensure better React rendering performance and avoid unintentional keys assigned to the Fragment, replace the short syntax with a proper <React.Fragment key={unique}> when necessary, or move the 'key' to the wrapper element.
94-124
: Allow dynamic sizing or scrolling
"NotificationBox" sets a fixed width of "320px" and a fixed layout. Consider using a more responsive or scrolling approach if there are numerous notifications.packages/extension-polkagate/src/popup/notification/NotificationSettings.tsx (2)
25-36
: Maintain consistent naming
"OptionButton" is a descriptive name. For readability and consistent naming, consider either using "OptionToggle" or "OptionSelector" if it is used to open selection popups. This helps unify naming patterns across the codebase, especially around toggles/selectors elsewhere.
91-100
: Handle scenario when there are no accounts
When "accounts" is empty in the context, ensure the UI gracefully handles toggling notification settings to avoid creating a mismatch in the state. For example, disable or hide toggles until at least one account is selected.packages/extension-polkagate/src/hooks/useNotifications.ts (3)
54-72
: Clarify first-time initialization vs. returning visitors
In the reducer, "INITIALIZE" sets isFirstTime to true, while "CHECK_FIRST_TIME" also sets it to true. Consider consolidating their usage or documenting the difference to avoid confusion about how the "isFirstTime" property is determined.
271-300
: Elaborate on error handling in "getReceivedFundsInformation"
When the API call returns a non-zero code, you raise a generic error and then retry. Consider logging the actual status code or response to help debugging issues related to partial or invalid data.
325-352
: Optimize final save
The "beforeunload" event is used to save notifications, but it may not always provide enough time on slow networks. Consider triggering earlier saves or listening to other lifecycle events (like tab switching) to improve reliability.packages/extension-polkagate/src/popup/notification/util.ts (1)
266-336
: Document or limit concurrency
"getReceivedFundsInformation" handles batched requests. Consider restricting concurrency at the batch or address level to avoid saturating subscan with small intervals between attempts, or consider providing user feedback if the calls are extensive.packages/extension-polkagate/src/popup/notification/constant.ts (3)
6-6
: Consider expanding ReferendaStatus type with more descriptive statusesThe current type definition might be too simplistic. Consider adding more descriptive statuses like 'executionFailed', 'scheduled', etc., to better represent the complete lifecycle of referenda.
-export type ReferendaStatus = 'ongoing' | 'approved' | 'timedOut' | 'rejected' | 'cancelled'; +export type ReferendaStatus = + | 'ongoing' + | 'approved' + | 'timedOut' + | 'rejected' + | 'cancelled' + | 'scheduled' + | 'executionFailed' + | 'executed';
8-11
: Consider making tracking limits configurableThe hardcoded tracking limits (
REFERENDA_COUNT_TO_TRACK_DOT
andREFERENDA_COUNT_TO_TRACK_KSM
) might need adjustment based on user feedback or system performance. Consider making these configurable through user settings.
33-61
: Consider organizing SUBSCAN_SUPPORTED_CHAINS more efficientlyThe current list of supported chains could be better organized for maintainability.
-export const SUBSCAN_SUPPORTED_CHAINS = [ +export const SUBSCAN_SUPPORTED_CHAINS = Object.freeze({ + RELAY_CHAINS: ['Polkadot', 'Kusama', 'Westend'], + ASSET_HUBS: ['PolkadotAssethub', 'KusamaAssethub', 'WestendAssethub', 'PaseoAssethub'], + PARACHAINS: [ 'Acala', 'Ajuna', 'Astar', // ... other parachains -]; +]});packages/extension-polkagate/src/popup/notification/partial/SelectAccounts.tsx (2)
14-18
: Consider improving Props interface documentationThe Props interface could benefit from JSDoc comments explaining the purpose of each prop.
interface Props { + /** Array of previously selected account addresses */ previousState: string[]; + /** Callback function triggered when selection is applied */ onApply: (items: string[]) => () => void; + /** Callback function to close the selection dialog */ onClose: () => void; }
68-72
: Add loading state to PButtonConsider adding a loading state to prevent multiple submissions while the apply action is processing.
<PButton _onClick={onApply(selected)} disabled={isSelectionUnchanged || selected.length > MAX_ACCOUNT_COUNT_NOTIFICATION} + loading={isApplying} text={t('Apply')} />
packages/extension-polkagate/src/popup/notification/partial/SelectNetwork.tsx (4)
23-24
: Move RELAY_CHAINS constant to constants fileThe
RELAY_CHAINS
constant should be moved to the constants file for better maintainability and reuse.
45-54
: Optimize isRelayChain functionThe
isRelayChain
function can be simplified and optimized.-const isRelayChain = useCallback((chain: string) => { - const itIs = RELAY_CHAINS.includes(chain); - const base = chain.charAt(0).toUpperCase() + chain.slice(1); - - if (!itIs) { - return base; - } else { - return base + ' Relay Chain'; - } -}, []); +const isRelayChain = useCallback((chain: string) => { + const base = chain.charAt(0).toUpperCase() + chain.slice(1); + return RELAY_CHAINS.includes(chain) ? `${base} Relay Chain` : base; +}, []);
67-83
: Consider extracting network option componentThe network option rendering logic could be extracted into a separate component for better maintainability.
+const NetworkOption = React.memo(({ option, isSelected, onSelect }) => ( + <Grid alignItems='center' container item justifyContent='space-between'> + <Grid alignItems='center' columnGap='10px' container item width='fit-content'> + <ChainLogo chainName={option.text} /> + <Typography fontSize='16px' fontWeight={400}> + {isRelayChain(option.text)} + </Typography> + </Grid> + <Switch + isChecked={isSelected} + onChange={onSelect} + theme={theme} + /> + </Grid> +));
1-112
: Consider unifying selection componentsBoth
SelectAccounts
andSelectNetwork
share similar patterns and could potentially be unified into a single, more generic selection component.Consider creating a generic
SelectionList
component that can be configured for different types of items (accounts, networks) while maintaining specific rendering logic through render props or children functions.packages/extension-polkagate/src/partials/SettingSubMenu.tsx (1)
118-126
: Consider consistent spacing with other MenuItems.While the implementation is correct, consider maintaining consistent vertical spacing with other menu items for better visual hierarchy.
<MenuItem fontSize='17px' iconComponent={ <NotificationsIcon sx={{ color: theme.palette.text.primary, fontSize: '23px' }} /> } onClick={onManageNotifications} - py='0px' + py='5px' text={t('Manage notifications')} />packages/extension-polkagate/src/components/SVG/HideBalance.tsx (1)
88-93
: LGTM: Improved accessibility with IconButton.Good use of Material-UI's IconButton component. Consider adding an aria-label to the IconButton for better accessibility.
-<IconButton onClick={onClick} sx={{ m: 0 }}> +<IconButton + aria-label={hide ? 'Show balance' : 'Hide balance'} + onClick={onClick} + sx={{ m: 0 }} +>packages/extension-polkagate/src/popup/home/YouHave.tsx (2)
94-111
: Consider responsive design for mobile views.The header icons are well-organized, but consider adding responsive spacing and potentially stacking icons vertically on smaller screens.
<Grid container item width='fit-content'> + <Grid + container + spacing={{ xs: 1, sm: 2 }} + direction={{ xs: 'column', sm: 'row' }} + alignItems='center' + > <HideBalance darkColor={theme.palette.secondary.light} hide={isHideNumbers} lightColor={theme.palette.secondary.light} onClick={toggleHideNumbers} size={20} /> <Notifications /> <Infotip2 placement='bottom' text={t('Menu options')}> <IconButton onClick={onMenuClick} sx={{ p: 0 }} > <MoreVertIcon sx={{ color: 'secondary.light', fontSize: '33px' }} /> </IconButton> </Infotip2> + </Grid> </Grid>
89-112
: Consider extracting header actions into a separate component.The header section with multiple actions (hide balance, notifications, menu) could be extracted into a separate component for better maintainability and reusability.
Consider creating a new component like
HeaderActions
to encapsulate these controls:interface HeaderActionsProps { isHideNumbers: boolean; toggleHideNumbers: () => void; onMenuClick: () => void; } const HeaderActions: React.FC<HeaderActionsProps> = ({ isHideNumbers, toggleHideNumbers, onMenuClick }) => { const theme = useTheme(); const { t } = useTranslation(); return ( <Grid container item width='fit-content'> <HideBalance darkColor={theme.palette.secondary.light} hide={isHideNumbers} lightColor={theme.palette.secondary.light} onClick={toggleHideNumbers} size={20} /> <Notifications /> <Infotip2 placement='bottom' text={t('Menu options')}> <IconButton onClick={onMenuClick} sx={{ p: 0 }}> <MoreVertIcon sx={{ color: 'secondary.light', fontSize: '33px' }} /> </IconButton> </Infotip2> </Grid> ); };packages/extension-polkagate/src/popup/notification/NotificationItem.tsx (3)
54-164
: Consider extracting notification type renderersThe
renderText
function is quite large and handles multiple notification types. Consider extracting each notification type renderer into a separate component for better maintainability and reusability.Example structure:
const ReceivedFundNotification = ({ message }) => { // receivedFund renderer logic }; const ReferendaNotification = ({ message }) => { // referenda renderer logic }; const StakingRewardNotification = ({ message }) => { // stakingReward renderer logic };
77-79
: Remove commented codeThere's commented code that should be removed if it's no longer needed.
147-153
: Remove commented code blockThere's a large commented code block that should be removed if it's no longer needed.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (18)
packages/extension-polkagate/src/components/SVG/HideBalance.tsx
(3 hunks)packages/extension-polkagate/src/hooks/index.ts
(1 hunks)packages/extension-polkagate/src/hooks/useNotifications.ts
(1 hunks)packages/extension-polkagate/src/partials/AccountMenu.tsx
(2 hunks)packages/extension-polkagate/src/partials/Menu.tsx
(6 hunks)packages/extension-polkagate/src/partials/SettingSubMenu.tsx
(3 hunks)packages/extension-polkagate/src/popup/home/YouHave.tsx
(3 hunks)packages/extension-polkagate/src/popup/notification/NotificationItem.tsx
(1 hunks)packages/extension-polkagate/src/popup/notification/NotificationSettings.tsx
(1 hunks)packages/extension-polkagate/src/popup/notification/Notifications.tsx
(1 hunks)packages/extension-polkagate/src/popup/notification/constant.ts
(1 hunks)packages/extension-polkagate/src/popup/notification/partial/SelectAccounts.tsx
(1 hunks)packages/extension-polkagate/src/popup/notification/partial/SelectNetwork.tsx
(1 hunks)packages/extension-polkagate/src/popup/notification/util.ts
(1 hunks)packages/extension-polkagate/src/util/workers/shared-helpers/getAssetOnRelayChain.js
(2 hunks)packages/extension-polkagate/src/util/workers/utils/index.ts
(1 hunks)packages/extension-polkagate/src/util/workers/utils/newReferendaNotification.js
(1 hunks)packages/extension-ui/src/Popup/routes/RouteDefinitions.tsx
(2 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
packages/extension-polkagate/src/popup/notification/NotificationItem.tsx
[error] 33-33: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.
See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.
(lint/suspicious/noGlobalIsNan)
packages/extension-polkagate/src/partials/Menu.tsx
[error] 162-174: Avoid using unnecessary Fragment.
A fragment is redundant if it contains only one child, or if it is the child of a html element, and is not a keyed fragment.
Unsafe fix: Remove the Fragment
(lint/complexity/noUselessFragments)
packages/extension-polkagate/src/popup/notification/util.ts
[error] 108-108: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.
See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.
(lint/suspicious/noGlobalIsNan)
🔇 Additional comments (22)
packages/extension-polkagate/src/partials/AccountMenu.tsx (1)
101-121
: Clarify relationship to notifications feature.
The PR objectives mention adding notifications, but the changes in this file are purely styling-related. Could you clarify how these styling changes relate to the notifications feature implementation?
packages/extension-polkagate/src/partials/Menu.tsx (10)
8-8
: Lock Icon import looks good.
No issues found with the addition of "LockIcon." Usage is correct.
13-19
: New imports are consistent.
The newly introduced imports appear to be correctly referenced and in line with the usage within the file.
37-40
: Enum usage appears clear and maintainable.
Defining an enum for popup states is a good approach.
42-42
: Transition component is well-defined.
Forward ref usage aligns with Material UI’s best practices.
52-53
: Hooks usage is correctly integrated.
“isLoginEnabled” and “setExtensionLock” are well-placed for extension lock functionality.
57-57
: Popup state initialization is straightforward.
Using a dedicated enum to switch popup modes is a clean solution.
94-105
: Testnet confirmation and rejection logic is coherent.
Setting the popup back to NONE and updating storage/toggling testnet usage is logically consistent and aligned with the intended feature.
120-126
: Dialog Paper and backdrop styling changes look fine.
This ensures a transparent overlay with a blur effect, enhancing the UI design.
Also applies to: 128-135
139-141
: Grid layout update is consistent with the new design.
The code correctly conditions the main menu content on “POPUP_MENUS.NONE.”
229-254
: Warning popup logic is well-structured.
The multi-step display for confirming or rejecting testnet enabling is clear and user-friendly.
packages/extension-polkagate/src/util/workers/utils/index.ts (1)
9-9
: LGTM!
The export follows the established pattern and is correctly configured.
packages/extension-polkagate/src/popup/notification/Notifications.tsx (1)
126-176
: Validate popover anchor element reference
A potential edge case occurs if onClick is triggered while the component unmounts or if anchorEl is null after re-renders. Ensure the anchorEl reference is valid before leveraging it in the Popover.
Run the script below to search for all references to "setAnchorEl" to ensure consistent usage and no stale references:
✅ Verification successful
Based on the search results, I can see that the setAnchorEl
usage in the Notifications component follows a consistent and safe pattern that's commonly used across the codebase for managing popover anchors. Specifically:
- The
anchorEl
state is properly initialized as null - It's set to the current target element in the click handler
- It's properly cleared in the close handler
- The Popover's open state is controlled by
Boolean(anchorEl)
- The same pattern is used consistently across multiple components in the codebase
The edge cases are properly handled
The implementation follows React's best practices for managing popover anchors and includes all necessary safeguards:
- Null checking through Boolean(anchorEl)
- Proper cleanup in the close handler
- Proper event target reference in the click handler
- Component state is managed through React's useState hook
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
rg -A 5 'setAnchorEl'
Length of output: 25864
packages/extension-polkagate/src/popup/notification/NotificationSettings.tsx (2)
38-52
: Consider more type-safety for storing chain values
"NotificationSettingType" allows arrays of "DropdownOption[]". However, nested checks are required. Consider narrowing the chain references to a known type or an enum for more robust type-safety.
168-171
: Clarify the navigation flow
"onNotificationApply" redirects to "/" after saving settings. Confirm that this redirect is desired and doesn't unexpectedly skip any steps if the user intended to continue configuring other settings.
packages/extension-polkagate/src/hooks/useNotifications.ts (1)
152-156
: Avoid accidental duplication
Multiple booleans and references track whether certain calls have occurred (e.g., isGettingReceivedFundRef, isGettingPayoutsRef, isSavingRef, etc.). Double-check there's no scenario where these references cause the logic to skip necessary calls or block subsequent updates.
packages/extension-polkagate/src/popup/notification/util.ts (1)
167-203
: Ensure "relay chain" and "network" are removed as expected
In "formatChainName", you remove " relay chain" and " network". Confirm that edge cases like "Acala Network" or multi-word suffixes still get sanitized correctly. If necessary, add more robust checks or expansions (e.g., " networks", " Relay Chains").
packages/extension-polkagate/src/popup/notification/constant.ts (1)
14-17
: Review retry and batch size parameters
The current values for MAX_RETRIES
(2) and BATCH_SIZE
(3) seem arbitrary. Please verify if these values are optimal for the notification system's performance and reliability.
packages/extension-polkagate/src/partials/SettingSubMenu.tsx (1)
8-8
: LGTM: Clean implementation of notification management navigation.
The notification icon import and callback implementation follow React best practices.
Also applies to: 54-56
packages/extension-polkagate/src/popup/notification/NotificationItem.tsx (1)
166-191
: LGTM: Component rendering looks good
The component's rendering logic is well-structured, using proper Material-UI components and conditional styling based on the read status.
packages/extension-polkagate/src/hooks/index.ts (1)
82-82
: LGTM: Clean hook export
The new hook export follows the existing pattern and maintains alphabetical ordering.
packages/extension-ui/src/Popup/routes/RouteDefinitions.tsx (1)
314-318
: LGTM: Route configuration is well-structured
The new route for notification settings follows the existing pattern and is correctly placed in the FEATURE_ROUTES array.
packages/extension-polkagate/src/util/workers/utils/newReferendaNotification.js
Show resolved
Hide resolved
export async function newRefNotif (api, chainName, port) { | ||
if (!NOTIFICATION_GOVERNANCE_CHAINS.includes(chainName)) { | ||
return undefined; | ||
} | ||
|
||
const isDot = chainName === 'polkadot'; | ||
|
||
const latestRefId = Number((await api.query['referenda']['referendumCount']()).toPrimitive()); | ||
|
||
const referendaInfoRequests = Array.from({ length: isDot ? REFERENDA_COUNT_TO_TRACK_DOT : REFERENDA_COUNT_TO_TRACK_KSM }, (_, index) => | ||
api.query['referenda']['referendumInfoFor'](latestRefId - (index + 1))); | ||
|
||
const referendaInfo = await Promise.all(referendaInfoRequests); | ||
|
||
const info = referendaInfo.map((item, index) => { | ||
const status = getRefStatus(item); | ||
|
||
return { | ||
refId: latestRefId - index, | ||
status | ||
}; | ||
}); | ||
|
||
const message = { | ||
functionName: NOTIFICATIONS_KEY, | ||
message: { | ||
chainGenesis: api.genesisHash.toHex(), | ||
data: info, | ||
type: 'referenda' | ||
} | ||
}; | ||
|
||
// console.log('message sent :', JSON.stringify(message)); | ||
port.postMessage(JSON.stringify(message)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling and rate limiting to newRefNotif
The function has several potential issues that need to be addressed:
- Missing error handling for API calls and Promise.all
- No rate limiting for parallel requests
- Potential performance impact with large number of tracked referenda
export async function newRefNotif (api, chainName, port) {
if (!NOTIFICATION_GOVERNANCE_CHAINS.includes(chainName)) {
return undefined;
}
const isDot = chainName === 'polkadot';
+ try {
const latestRefId = Number((await api.query['referenda']['referendumCount']()).toPrimitive());
const referendaInfoRequests = Array.from({ length: isDot ? REFERENDA_COUNT_TO_TRACK_DOT : REFERENDA_COUNT_TO_TRACK_KSM }, (_, index) =>
api.query['referenda']['referendumInfoFor'](latestRefId - (index + 1)));
- const referendaInfo = await Promise.all(referendaInfoRequests);
+ // Add rate limiting to avoid overwhelming the node
+ const referendaInfo = await Promise.all(
+ referendaInfoRequests.map((promise, index) =>
+ new Promise(resolve =>
+ setTimeout(() => resolve(promise), index * 100)
+ )
+ )
+ );
const info = referendaInfo.map((item, index) => {
const status = getRefStatus(item);
return {
refId: latestRefId - index,
status
};
});
const message = {
functionName: NOTIFICATIONS_KEY,
message: {
chainGenesis: api.genesisHash.toHex(),
data: info,
type: 'referenda'
}
};
port.postMessage(JSON.stringify(message));
+ } catch (error) {
+ console.error('Error in newRefNotif:', error);
+ port.postMessage(JSON.stringify({
+ functionName: NOTIFICATIONS_KEY,
+ error: 'Failed to fetch referenda information'
+ }));
+ }
}
📝 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.
export async function newRefNotif (api, chainName, port) { | |
if (!NOTIFICATION_GOVERNANCE_CHAINS.includes(chainName)) { | |
return undefined; | |
} | |
const isDot = chainName === 'polkadot'; | |
const latestRefId = Number((await api.query['referenda']['referendumCount']()).toPrimitive()); | |
const referendaInfoRequests = Array.from({ length: isDot ? REFERENDA_COUNT_TO_TRACK_DOT : REFERENDA_COUNT_TO_TRACK_KSM }, (_, index) => | |
api.query['referenda']['referendumInfoFor'](latestRefId - (index + 1))); | |
const referendaInfo = await Promise.all(referendaInfoRequests); | |
const info = referendaInfo.map((item, index) => { | |
const status = getRefStatus(item); | |
return { | |
refId: latestRefId - index, | |
status | |
}; | |
}); | |
const message = { | |
functionName: NOTIFICATIONS_KEY, | |
message: { | |
chainGenesis: api.genesisHash.toHex(), | |
data: info, | |
type: 'referenda' | |
} | |
}; | |
// console.log('message sent :', JSON.stringify(message)); | |
port.postMessage(JSON.stringify(message)); | |
} | |
export async function newRefNotif (api, chainName, port) { | |
if (!NOTIFICATION_GOVERNANCE_CHAINS.includes(chainName)) { | |
return undefined; | |
} | |
const isDot = chainName === 'polkadot'; | |
try { | |
const latestRefId = Number((await api.query['referenda']['referendumCount']()).toPrimitive()); | |
const referendaInfoRequests = Array.from({ length: isDot ? REFERENDA_COUNT_TO_TRACK_DOT : REFERENDA_COUNT_TO_TRACK_KSM }, (_, index) => | |
api.query['referenda']['referendumInfoFor'](latestRefId - (index + 1))); | |
// Add rate limiting to avoid overwhelming the node | |
const referendaInfo = await Promise.all( | |
referendaInfoRequests.map((promise, index) => | |
new Promise(resolve => | |
setTimeout(() => resolve(promise), index * 100) | |
) | |
) | |
); | |
const info = referendaInfo.map((item, index) => { | |
const status = getRefStatus(item); | |
return { | |
refId: latestRefId - index, | |
status | |
}; | |
}); | |
const message = { | |
functionName: NOTIFICATIONS_KEY, | |
message: { | |
chainGenesis: api.genesisHash.toHex(), | |
data: info, | |
type: 'referenda' | |
} | |
}; | |
port.postMessage(JSON.stringify(message)); | |
} catch (error) { | |
console.error('Error in newRefNotif:', error); | |
port.postMessage(JSON.stringify({ | |
functionName: NOTIFICATIONS_KEY, | |
error: 'Failed to fetch referenda information' | |
})); | |
} | |
} |
packages/extension-polkagate/src/util/workers/shared-helpers/getAssetOnRelayChain.js
Show resolved
Hide resolved
export function timestampToDate (timestamp: number | string, format: 'full' | 'short' | 'relative' = 'full'): string { | ||
// Ensure timestamp is a number and convert if it's a string | ||
const timestampNum = Number(timestamp); | ||
|
||
// Check if timestamp is valid | ||
if (isNaN(timestampNum)) { | ||
return 'Invalid Timestamp'; | ||
} | ||
|
||
// Create a Date object (multiply by 1000 if it's a Unix timestamp in seconds) | ||
const date = new Date(timestampNum * 1000); | ||
|
||
// Different formatting options | ||
switch (format) { | ||
case 'full': | ||
return date.toLocaleString('en-US', { | ||
day: 'numeric', | ||
hour: '2-digit', | ||
minute: '2-digit', | ||
month: 'long', | ||
second: '2-digit' | ||
}); | ||
|
||
case 'short': | ||
return date.toLocaleString('en-US', { | ||
day: 'numeric', | ||
month: 'short' | ||
}); | ||
|
||
case 'relative': | ||
return getRelativeTime(date); | ||
|
||
default: | ||
return date.toLocaleString(); | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use Number.isNaN for type safety
Static analysis warns that global isNaN() is unsafe due to type coercion. Replace:
if (isNaN(timestampNum)) { ... }
with:
if (Number.isNaN(timestampNum)) { ... }
This ensures a more robust check consistent with modern JavaScript best practices.
Apply this diff:
- if (isNaN(timestampNum)) {
+ if (Number.isNaN(timestampNum)) {
return 'Invalid Timestamp';
}
📝 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.
export function timestampToDate (timestamp: number | string, format: 'full' | 'short' | 'relative' = 'full'): string { | |
// Ensure timestamp is a number and convert if it's a string | |
const timestampNum = Number(timestamp); | |
// Check if timestamp is valid | |
if (isNaN(timestampNum)) { | |
return 'Invalid Timestamp'; | |
} | |
// Create a Date object (multiply by 1000 if it's a Unix timestamp in seconds) | |
const date = new Date(timestampNum * 1000); | |
// Different formatting options | |
switch (format) { | |
case 'full': | |
return date.toLocaleString('en-US', { | |
day: 'numeric', | |
hour: '2-digit', | |
minute: '2-digit', | |
month: 'long', | |
second: '2-digit' | |
}); | |
case 'short': | |
return date.toLocaleString('en-US', { | |
day: 'numeric', | |
month: 'short' | |
}); | |
case 'relative': | |
return getRelativeTime(date); | |
default: | |
return date.toLocaleString(); | |
} | |
} | |
export function timestampToDate (timestamp: number | string, format: 'full' | 'short' | 'relative' = 'full'): string { | |
// Ensure timestamp is a number and convert if it's a string | |
const timestampNum = Number(timestamp); | |
// Check if timestamp is valid | |
if (Number.isNaN(timestampNum)) { | |
return 'Invalid Timestamp'; | |
} | |
// Create a Date object (multiply by 1000 if it's a Unix timestamp in seconds) | |
const date = new Date(timestampNum * 1000); | |
// Different formatting options | |
switch (format) { | |
case 'full': | |
return date.toLocaleString('en-US', { | |
day: 'numeric', | |
hour: '2-digit', | |
minute: '2-digit', | |
month: 'long', | |
second: '2-digit' | |
}); | |
case 'short': | |
return date.toLocaleString('en-US', { | |
day: 'numeric', | |
month: 'short' | |
}); | |
case 'relative': | |
return getRelativeTime(date); | |
default: | |
return date.toLocaleString(); | |
} | |
} |
🧰 Tools
🪛 Biome (1.9.4)
[error] 108-108: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.
See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.
(lint/suspicious/noGlobalIsNan)
export const DEFAULT_NOTIFICATION_SETTING = { | ||
accounts: [], | ||
enable: true, | ||
governance: DEFAULT_SUPPORTED_CHAINS, | ||
receivedFunds: true, | ||
stakingRewards: DEFAULT_SUPPORTED_CHAINS | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider adding validation for notification settings
The DEFAULT_NOTIFICATION_SETTING
object should be validated when used. Consider adding runtime type checking or validation.
+export interface NotificationSetting {
+ accounts: string[];
+ enable: boolean;
+ governance: typeof DEFAULT_SUPPORTED_CHAINS;
+ receivedFunds: boolean;
+ stakingRewards: typeof DEFAULT_SUPPORTED_CHAINS;
+}
export const DEFAULT_NOTIFICATION_SETTING = {
accounts: [],
enable: true,
governance: DEFAULT_SUPPORTED_CHAINS,
receivedFunds: true,
stakingRewards: DEFAULT_SUPPORTED_CHAINS
-};
+} as const satisfies NotificationSetting;
📝 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.
export const DEFAULT_NOTIFICATION_SETTING = { | |
accounts: [], | |
enable: true, | |
governance: DEFAULT_SUPPORTED_CHAINS, | |
receivedFunds: true, | |
stakingRewards: DEFAULT_SUPPORTED_CHAINS | |
}; | |
export interface NotificationSetting { | |
accounts: string[]; | |
enable: boolean; | |
governance: typeof DEFAULT_SUPPORTED_CHAINS; | |
receivedFunds: boolean; | |
stakingRewards: typeof DEFAULT_SUPPORTED_CHAINS; | |
} | |
export const DEFAULT_NOTIFICATION_SETTING = { | |
accounts: [], | |
enable: true, | |
governance: DEFAULT_SUPPORTED_CHAINS, | |
receivedFunds: true, | |
stakingRewards: DEFAULT_SUPPORTED_CHAINS | |
} as const satisfies NotificationSetting; |
<Grid alignItems='center' container item sx={{ maxHeight: '320px', overflowY: 'scroll', p: '30px 15px 0' }}> | ||
{accounts.map(({ address }, index) => ( | ||
<Grid container item key={index}> | ||
<Grid alignItems='center' container item justifyContent='left' xs={10}> | ||
<Identity address={address} identiconSize={25} showShortAddress showSocial={false} style={{ fontSize: '14px' }} subIdOnly /> | ||
</Grid> | ||
<Grid alignItems='center' container height='100%' item justifyContent='center' xs={2}> | ||
<Switch | ||
isChecked={selected.includes(address)} | ||
onChange={onSelect(address)} | ||
theme={theme} | ||
/> | ||
</Grid> | ||
</Grid> | ||
))} | ||
</Grid> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Add keyboard navigation support
The account selection list should support keyboard navigation for better accessibility.
<Grid alignItems='center' container item sx={{ maxHeight: '320px', overflowY: 'scroll', p: '30px 15px 0' }}>
+ <div role="listbox" tabIndex={0}>
{accounts.map(({ address }, index) => (
- <Grid container item key={index}>
+ <Grid
+ container
+ item
+ key={address}
+ role="option"
+ aria-selected={selected.includes(address)}
+ tabIndex={-1}
+ onKeyPress={(e) => e.key === 'Enter' && onSelect(address)()}
+ >
Committable suggestion skipped: line range outside the PR's diff.
const sanitizeBNAmount = useCallback((amount: string, decimal: number) => { | ||
const bnAmount = Number(amount); | ||
|
||
return isNaN(bnAmount) | ||
? amount | ||
: (bnAmount / (10 ** decimal)).toString(); | ||
}, []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replace global isNaN with Number.isNaN
The global isNaN
function performs type coercion which can lead to unexpected results. Use Number.isNaN
for more reliable number validation.
- return isNaN(bnAmount)
+ return Number.isNaN(bnAmount)
📝 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.
const sanitizeBNAmount = useCallback((amount: string, decimal: number) => { | |
const bnAmount = Number(amount); | |
return isNaN(bnAmount) | |
? amount | |
: (bnAmount / (10 ** decimal)).toString(); | |
}, []); | |
const sanitizeBNAmount = useCallback((amount: string, decimal: number) => { | |
const bnAmount = Number(amount); | |
return Number.isNaN(bnAmount) | |
? amount | |
: (bnAmount / (10 ** decimal)).toString(); | |
}, []); |
🧰 Tools
🪛 Biome (1.9.4)
[error] 33-33: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.
See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.
(lint/suspicious/noGlobalIsNan)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (4)
packages/extension-polkagate/src/hooks/useNotifications.ts (2)
72-129
: Consider extracting timestamp conversion to a constant.The timestamp conversion from milliseconds to seconds appears in multiple places. Consider extracting
1000
to a named constant likeMILLISECONDS_TO_SECONDS
for better maintainability and clarity.+const MILLISECONDS_TO_SECONDS = 1000; + const notificationReducer = ( state: NotificationsType, action: NotificationActionType ): NotificationsType => { switch (action.type) { case 'INITIALIZE': return { isFirstTime: true, - latestLoggedIn: Math.floor(Date.now() / 1000), + latestLoggedIn: Math.floor(Date.now() / MILLISECONDS_TO_SECONDS), notificationMessages: [], // ... rest of the code
257-301
: Refactor worker message handling for better maintainability.The message handling logic has multiple nested conditions. Consider extracting the message validation into a separate function.
+const isValidWorkerMessage = (data: unknown): data is WorkerMessage => { + if (!data) return false; + const msg = data as WorkerMessage; + return msg.functionName === NOTIFICATIONS_KEY && + msg.message?.type === 'referenda'; +}; + useEffect(() => { if (notificationIsOff || settings?.governance?.length === 0) { return; } const handelMessage = (event: MessageEvent<string>) => { try { - if (!event.data) { - return; - } - const parsedMessage = JSON.parse(event.data) as WorkerMessage; - - if (parsedMessage.functionName !== NOTIFICATIONS_KEY) { - return; - } - - const { message } = parsedMessage; - - if (message.type !== 'referenda') { + if (!isValidWorkerMessage(parsedMessage)) { return; }packages/extension-polkagate/src/popup/notification/util.ts (2)
176-204
: Enhance formatChainName function robustness.The function could be more robust by handling edge cases and providing better type safety.
const formatChainName = (chainName: string | undefined, suffixes: string[] = ['asset hub']): string => { if (!chainName) { return ''; } + // Trim whitespace and convert to lowercase + const normalizedName = chainName.trim().toLowerCase(); + + if (normalizedName.length === 0) { + return ''; + } // Find and remove the invalid suffixes - const sanitized = chainName.toLowerCase().replace(' relay chain', '').replace(' network', ''); + const sanitized = normalizedName + .replace(/\s+relay\s+chain\b/g, '') + .replace(/\s+network\b/g, '');
550-554
: Improve type safety and naming in updateReferendas function.The function could benefit from better parameter naming and type handling.
-export const updateReferendas = (preciousRefs: ReferendaNotificationType[] | null | undefined, newRefs: ReferendaNotificationType[], network: string) => { +export const updateReferendas = ( + previousRefs: ReferendaNotificationType[] | null | undefined, + newRefs: ReferendaNotificationType[], + networkName: string +): ReferendaNotificationType[] => { + const normalizedNetworkName = networkName.toLowerCase(); + const filteredRefs = previousRefs?.filter( + ({ chainName }) => chainName.toLowerCase() !== normalizedNetworkName + ) ?? []; + + return [...filteredRefs, ...newRefs]; +};
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
packages/extension-polkagate/src/hooks/useNotifications.ts
(1 hunks)packages/extension-polkagate/src/popup/notification/util.ts
(1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
packages/extension-polkagate/src/popup/notification/util.ts
[error] 108-108: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.
See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.
(lint/suspicious/noGlobalIsNan)
🔇 Additional comments (3)
packages/extension-polkagate/src/hooks/useNotifications.ts (1)
28-70
: LGTM! Well-structured interfaces and initial state.
The interfaces provide good type safety and the initial state is properly structured with undefined values.
packages/extension-polkagate/src/popup/notification/util.ts (2)
103-139
: Use Number.isNaN for type-safe number validation.
The global isNaN() is unsafe due to type coercion. Replace with Number.isNaN() for type safety.
- if (isNaN(timestampNum)) {
+ if (Number.isNaN(timestampNum)) {
return 'Invalid Timestamp';
}
🧰 Tools
🪛 Biome (1.9.4)
[error] 108-108: isNaN is unsafe. It attempts a type coercion. Use Number.isNaN instead.
See the MDN documentation for more details.
Unsafe fix: Use Number.isNaN instead.
(lint/suspicious/noGlobalIsNan)
272-408
: Extract common retry logic into a reusable function.
Both API functions contain similar retry logic. Consider extracting it into a reusable utility function.
+async function fetchWithRetry<T>(
+ operation: () => Promise<T>,
+ context: string,
+ maxRetries: number = MAX_RETRIES
+): Promise<T | null> {
+ let lastError: unknown = null;
+
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
+ try {
+ return await operation();
+ } catch (error) {
+ lastError = error;
+ console.warn(`Attempt ${attempt} failed for ${context}. Retrying...`);
+ await new Promise(resolve => setTimeout(resolve, attempt * 1000));
+ }
+ }
+
+ console.error(`Failed after ${maxRetries} attempts for ${context}`, lastError);
+ return null;
+}
useEffect(() => { | ||
const getSettings = async () => { | ||
const savedSettings = await getStorage(NOTIFICATION_SETTING_KEY) as NotificationSettingType; | ||
|
||
if (!savedSettings) { | ||
setDefaultSettingFlag(true); | ||
|
||
return; | ||
} | ||
|
||
setSettings(savedSettings); | ||
}; | ||
|
||
getSettings().catch(console.error); | ||
}, []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve error handling in settings retrieval.
The error is only logged to console. Consider implementing proper error handling and user feedback for settings retrieval failures.
- getSettings().catch(console.error);
+ getSettings().catch((error) => {
+ console.error('Failed to load notification settings:', error);
+ // TODO: Implement user feedback mechanism
+ setSettings(DEFAULT_NOTIFICATION_SETTING);
+ });
Committable suggestion skipped: line range outside the PR's diff.
useEffect(() => { | ||
const handleBeforeUnload = () => { | ||
const notificationIsInitializing = notifications.isFirstTime && notifications.referendas?.length === 0; | ||
|
||
if (!isSavingRef.current && !notificationIsInitializing) { | ||
isSavingRef.current = true; | ||
|
||
const dataToSave = notifications; | ||
|
||
dataToSave.latestLoggedIn = Math.floor(Date.now() / 1000); // timestamp must be in seconds not in milliseconds | ||
|
||
setStorage(NOTIFICATIONS_KEY, dataToSave) | ||
.then(() => { | ||
console.log('Notifications saved successfully on unload.'); | ||
}) | ||
.catch((error) => { | ||
console.error('Failed to save notifications on unload:', error); | ||
}); | ||
} | ||
}; | ||
|
||
// Add event listener for the 'beforeunload' event | ||
window.addEventListener('beforeunload', handleBeforeUnload); | ||
|
||
return () => { | ||
window.removeEventListener('beforeunload', handleBeforeUnload); | ||
}; | ||
}, [notifications]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential race condition in beforeunload handler.
The beforeunload handler might have a race condition when saving notifications. Consider using a synchronous approach or implementing a queue mechanism.
const handleBeforeUnload = () => {
const notificationIsInitializing = notifications.isFirstTime && notifications.referendas?.length === 0;
if (!isSavingRef.current && !notificationIsInitializing) {
isSavingRef.current = true;
const dataToSave = notifications;
dataToSave.latestLoggedIn = Math.floor(Date.now() / 1000);
- setStorage(NOTIFICATIONS_KEY, dataToSave)
- .then(() => {
- console.log('Notifications saved successfully on unload.');
- })
- .catch((error) => {
- console.error('Failed to save notifications on unload:', error);
- });
+ try {
+ // Use synchronous storage API if available
+ localStorage.setItem(NOTIFICATIONS_KEY, JSON.stringify(dataToSave));
+ } catch (error) {
+ console.error('Failed to save notifications on unload:', error);
+ } finally {
+ isSavingRef.current = false;
+ }
}
};
Committable suggestion skipped: line range outside the PR's diff.
const transformPayouts = (address: string, payouts: Payout[], network: DropdownOption) => { | ||
// Initialize the accumulator for the reduce function | ||
const initialAccumulator = { | ||
address, | ||
data: [] as PayoutsProp[], | ||
network | ||
}; | ||
const decimal = selectableNetworks.find(({ genesisHash }) => (genesisHash[0] as unknown as string) === network.value)?.decimals[0]; | ||
|
||
// Sanitize each transfer item and accumulate results | ||
const result = payouts.reduce((accumulator, payout) => { | ||
const sanitizedTransfer = { | ||
amount: payout.amount, | ||
date: timestampToDate(payout.block_timestamp), | ||
decimal, | ||
era: payout.era, | ||
timestamp: payout.block_timestamp, | ||
validatorStash: payout.validator_stash | ||
} as PayoutsProp; | ||
|
||
accumulator.data.push(sanitizedTransfer); | ||
|
||
return accumulator; | ||
}, initialAccumulator); | ||
|
||
return result; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Improve decimal handling and error cases in transformPayouts.
The function should handle cases where decimal is undefined and provide better type safety.
const transformPayouts = (address: string, payouts: Payout[], network: DropdownOption) => {
const initialAccumulator = {
address,
data: [] as PayoutsProp[],
network
};
- const decimal = selectableNetworks.find(({ genesisHash }) =>
+ const networkInfo = selectableNetworks.find(({ genesisHash }) =>
(genesisHash[0] as unknown as string) === network.value)?.decimals[0];
+
+ if (!networkInfo) {
+ console.warn(`Network decimals not found for ${network.value}`);
+ return initialAccumulator;
+ }
const result = payouts.reduce((accumulator, payout) => {
const sanitizedTransfer = {
amount: payout.amount,
date: timestampToDate(payout.block_timestamp),
- decimal,
+ decimal: networkInfo,
era: payout.era,
📝 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.
const transformPayouts = (address: string, payouts: Payout[], network: DropdownOption) => { | |
// Initialize the accumulator for the reduce function | |
const initialAccumulator = { | |
address, | |
data: [] as PayoutsProp[], | |
network | |
}; | |
const decimal = selectableNetworks.find(({ genesisHash }) => (genesisHash[0] as unknown as string) === network.value)?.decimals[0]; | |
// Sanitize each transfer item and accumulate results | |
const result = payouts.reduce((accumulator, payout) => { | |
const sanitizedTransfer = { | |
amount: payout.amount, | |
date: timestampToDate(payout.block_timestamp), | |
decimal, | |
era: payout.era, | |
timestamp: payout.block_timestamp, | |
validatorStash: payout.validator_stash | |
} as PayoutsProp; | |
accumulator.data.push(sanitizedTransfer); | |
return accumulator; | |
}, initialAccumulator); | |
return result; | |
}; | |
const transformPayouts = (address: string, payouts: Payout[], network: DropdownOption) => { | |
const initialAccumulator = { | |
address, | |
data: [] as PayoutsProp[], | |
network | |
}; | |
const networkInfo = selectableNetworks.find(({ genesisHash }) => (genesisHash[0] as unknown as string) === network.value)?.decimals[0]; | |
if (!networkInfo) { | |
console.warn(`Network decimals not found for ${network.value}`); | |
return initialAccumulator; | |
} | |
const result = payouts.reduce((accumulator, payout) => { | |
const sanitizedTransfer = { | |
amount: payout.amount, | |
date: timestampToDate(payout.block_timestamp), | |
decimal: networkInfo, | |
era: payout.era, | |
timestamp: payout.block_timestamp, | |
validatorStash: payout.validator_stash | |
} as PayoutsProp; | |
accumulator.data.push(sanitizedTransfer); | |
return accumulator; | |
}, initialAccumulator); | |
return result; | |
}; |
Close: #1688
Summary by CodeRabbit
New Features
HideBalance
component with improved accessibility features.SelectAccounts
component for account selection.SelectNetwork
component for selecting networks.NotificationItem
andNotifications
components for displaying notification messages.Bug Fixes
Documentation
Style
AccountMenu
andYouHave
components.Chores