Skip to content

feat: use Slider for Conviction settings #1629

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

Merged
merged 4 commits into from
Nov 6, 2024
Merged

Conversation

Nick-1979
Copy link
Member

@Nick-1979 Nick-1979 commented Nov 5, 2024

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced Convictions component with a new slider for selecting conviction levels, improving user interaction.
    • Introduced DraggableModalWithTitle component for a more user-friendly modal experience.
    • Updated themes with expanded color palettes, including new properties for success states.
    • Added new localization keys for better user guidance on token locking and window manipulation.
  • Bug Fixes

    • Improved handling of conviction options and state management in the voting process.
  • Documentation

    • Updated TypeScript declarations for theme properties to provide better customization options.
  • Style

    • Refined styling for modal components to enhance user experience.
    • Adjusted font sizes and layout in the Delegate component for improved readability.

@Nick-1979 Nick-1979 requested a review from AMIRKHANEF November 5, 2024 08:09
Copy link
Contributor

coderabbitai bot commented Nov 5, 2024

Caution

Review failed

The pull request is closed.

Walkthrough

The pull request introduces significant updates to the Convictions component and related files, enhancing functionality and styling. Key changes include the addition of a Slider component and new imports from @mui/material, a revised useConvictionOptions hook, and updates to the Cast component to improve voting logic. Theme files for both light and dark modes have been modified to include new color properties. Additionally, new components like DraggableModalWithTitle and ModalTitleWithDrag have been introduced, collectively aiming to streamline user interactions and improve the maintainability of the codebase.

Changes

File Path Change Summary
packages/extension-polkagate/src/components/Convictions.tsx Added imports for Slider, Typography, and useTheme. Updated function signature to include DEFAULT_CONVICTION. Modified onChangeConviction logic and introduced new state variables for max and min values. Replaced Select with Slider and updated return structure.
packages/extension-polkagate/src/fullscreen/governance/post/castVote/Cast.tsx Removed import for Vote type. Imported DEFAULT_CONVICTION from Convictions. Updated props and internal logic for previousVote. Introduced useInfo hook for state management and refined rendering logic for voting buttons.
packages/extension-polkagate/src/hooks/useConvictionOptions.ts Changed return type to DropdownOption[]. Removed t: TFunction parameter. Simplified state management and adjusted property access for conviction data.
packages/extension-polkagate/src/themes/dark.ts Expanded Palette and PaletteOptions interfaces with new properties. Updated success property in darkTheme to include a light variant.
packages/extension-polkagate/src/themes/light.ts Similar updates as in dark.ts, adding new properties to Palette and PaletteOptions, and updating success property in lightTheme to include a light variant.
packages/extension-polkagate/src/fullscreen/governance/components/DraggableModalWithTitle.tsx Introduced a new component for a draggable modal with a title, supporting various props for customization and drag handling.
packages/extension-polkagate/src/fullscreen/governance/post/castVote/index.tsx Replaced DraggableModal with DraggableModalWithTitle, integrating title management and adjusting the modal's close logic.
packages/extension-polkagate/src/fullscreen/partials/ModalTitleWithDrag.tsx Added a new component for a draggable title bar in modals, supporting drag events and conditional rendering of icons.
packages/extension-polkagate/src/fullscreen/governance/delegate/index.tsx Replaced DraggableModal with DraggableModalWithTitle, enhancing modal functionality with title and icon props.
packages/extension/polkagate/src/fullscreen/governance/delegate/delegate/Delegate.tsx Adjusted layout and font sizes for Convictions component, refined state management in useEffect, and simplified event handling functions.
packages/extension/public/locales/en/translation.json Added new localization keys for token locking and window dragging instructions.

Possibly related PRs

Suggested reviewers

  • AMIRKHANEF

🐰 In the land of code, where changes abound,
A slider now dances, with options profound.
Convictions are clear, with themes that delight,
In light and in dark, they shine ever bright.
So hop along, friends, let’s test and explore,
For in this new world, there’s always much more! 🌟


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?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Outside diff range and nitpick comments (10)
packages/extension-polkagate/src/themes/light.ts (2)

Line range hint 11-27: Consider renaming 'backgroundFL' to be more descriptive.

While the voting-related palette properties (aye, nay, approval, support) are self-explanatory, the purpose of 'backgroundFL' is not immediately clear from its name. Consider using a more descriptive name that indicates its specific use case.

The rest of the interface declarations look good and properly type the new palette properties.


Line range hint 29-44: LGTM! Consider adding color documentation.

The color choices are well-thought-out:

  • Good contrast between aye (#008080) and nay (#FF5722) colors
  • Appropriate success light variant (#46890C)
  • Consistent color scheme throughout

Consider adding a comment block documenting the purpose of each color, especially for the voting-related palette properties, to help maintain consistency in future theme updates.

packages/extension-polkagate/src/themes/dark.ts (2)

Line range hint 11-26: Fix type inconsistency in backgroundFL property.

The backgroundFL property has inconsistent types between the Palette and PaletteOptions interfaces:

  • In Palette: backgroundFL?: Palette['primary']
  • In PaletteOptions: backgroundFL?: Partial<TypeText>

This mismatch could lead to type-related issues during theme customization.

Apply this fix:

  interface Palette {
    aye: Palette['primary'];
    nay: Palette['primary'];
    label: Palette['primary'];
    approval: Palette['primary'];
    support: Palette['primary'];
-   backgroundFL?: Palette['primary'];
+   backgroundFL: TypeText;
  }

Theme properties need standardization and hardcoded colors should be migrated

Based on the verification results:

  1. The theme properties (aye, nay, approval, support, backgroundFL) are consistently used in governance and staking-related components for their intended purposes.
  2. However, there are numerous instances of hardcoded colors (especially #BA2882 and #99004F) across components that should be using theme properties instead.

Key areas needing attention:

  • Icons and SVG components frequently use hardcoded #BA2882
  • Circle components consistently use hardcoded #99004F
  • Arrow indicators and navigation elements use hardcoded stroke colors

Recommendations:

  1. Create semantic theme properties for commonly used colors (e.g., iconStroke, circleBackground)
  2. Migrate hardcoded colors in components to use theme properties
  3. Document the color system in a style guide
🔗 Analysis chain

Line range hint 11-47: Consider documenting color usage guidelines.

With the introduction of specialized color properties for voting-related UI elements (aye, nay, approval, etc.), consider:

  1. Documenting the intended usage of each color property
  2. Creating a style guide for consistent application across components
  3. Adding color contrast verification in your CI pipeline

This will help maintain consistency as the voting UI evolves.

Let's check for consistent usage of these new color properties:


Let me analyze the usage of these color properties more deeply to verify consistency.


Let me modify the search to avoid the file type issue.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the usage of new color palette properties across components

# Search for usage of new color properties
echo "Checking usage of new color properties..."
rg -l "palette\.(aye|nay|approval|support|backgroundFL)"

# Look for potential direct color usage instead of theme properties
echo "Checking for hardcoded colors that might need migration to theme..."
rg -l "(#99004F|#BA2882|#BCE2DB|#BA82A4)"

Length of output: 4491


Script:

#!/bin/bash
# Check the actual implementation of components using these theme properties
echo "=== Checking implementation of components using theme properties ==="
rg -A 2 "palette\.(aye|nay|approval|support|backgroundFL)" --type tsx

echo -e "\n=== Checking hardcoded color usage in components ==="
# Excluding theme files and SVG assets to focus on component implementations
rg "#(99004F|BA2882|BCE2DB|BA82A4)" --type tsx | grep -v "themes/" | grep -v "assets/"

Length of output: 480


Script:

#!/bin/bash
# Check the actual implementation of components using theme properties
echo "=== Checking implementation of components using theme properties ==="
rg -A 2 "palette\.(aye|nay|approval|support|backgroundFL)"

echo -e "\n=== Checking hardcoded color usage in components ==="
# Excluding theme files and SVG assets to focus on component implementations
rg "#(99004F|BA2882|BCE2DB|BA82A4)" | grep -v "themes/" | grep -v "\.svg:"

Length of output: 18989

packages/extension-polkagate/src/hooks/useConvictionOptions.ts (3)

10-13: LGTM! Consider organizing type imports.

The type definitions and hook signature changes improve type safety and follow React best practices by using the useTranslation hook instead of passing the translation function as a parameter.

Consider grouping related type imports together:

-import type { AccountId } from '@polkadot/types/interfaces/runtime';
-import type { BN } from '@polkadot/util';
-import type { DropdownOption } from '../util/types';
+// External types
+import type { AccountId } from '@polkadot/types/interfaces/runtime';
+import type { BN } from '@polkadot/util';
+
+// Internal types
+import type { DropdownOption } from '../util/types';

Also applies to: 22-24


Line range hint 47-54: Consider improving storage handling.

While the storage operations work, there are several potential improvements that could make the code more robust and maintainable:

  1. Abstract storage operations into a utility:
// storage.ts
const STORAGE_KEY = 'Convictions';

interface ConvictionStorage {
  [genesisHash: string]: DropdownOption[];
}

export const saveConvictions = async (
  genesisHash: string,
  options: DropdownOption[]
): Promise<void> => {
  const result = await chrome.storage.local.get(STORAGE_KEY);
  const current = (result[STORAGE_KEY] || {}) as ConvictionStorage;
  
  await chrome.storage.local.set({
    [STORAGE_KEY]: {
      ...current,
      [genesisHash]: options
    }
  });
};

export const loadConvictions = async (
  genesisHash: string
): Promise<DropdownOption[] | undefined> => {
  const result = await chrome.storage.local.get(STORAGE_KEY);
  return (result[STORAGE_KEY] as ConvictionStorage)?.[genesisHash];
};
  1. Add error handling for storage operations:
try {
  await saveConvictions(genesisHash, options);
} catch (error) {
  console.error('Failed to save convictions:', error);
}

Also applies to: 75-84


Line range hint 30-45: Consider extracting period calculation logic.

The conviction options generation logic could be more maintainable with some refactoring:

const formatPeriod = (
  blockTime: BN,
  voteLockingPeriod: BN,
  durationBn: BN
): string => {
  if (!voteLockingPeriod || voteLockingPeriod.lte(BN_ZERO)) {
    return '';
  }

  const [, formattedTime] = calcBlockTime(
    blockTime,
    durationBn.mul(voteLockingPeriod),
    t
  );

  return ` (${formattedTime})`;
};

const getConvictionOptions = useCallback((
  blockTime: BN,
  voteLockingPeriod: BN,
  genesisHash: string
) => {
  const options = [
    {
      text: t('0.1x voting balance, no lockup period'),
      value: 0.1
    },
    ...CONVICTIONS.map(([value, duration, durationBn]) => ({
      text: t('{{value}}x voting balance, locked for {{duration}}x duration{{period}}', {
        replace: {
          duration,
          period: formatPeriod(blockTime, voteLockingPeriod, durationBn),
          value
        }
      }),
      value
    }))
  ];
  // ... rest of the function
}, [t]);
packages/extension-polkagate/src/fullscreen/governance/post/castVote/Cast.tsx (3)

Line range hint 54-77: Optimize lock sorting logic.

The current implementation performs two separate sorts which is inefficient. Consider combining the sorting criteria into a single sort operation.

 function getAlreadyLockedValue (allBalances: DeriveBalancesAll | undefined): BN | undefined {
   const sortedLocks = allBalances?.lockedBreakdown
-    // first sort by amount, so greatest value first
-    .sort((a, b) =>
-      b.amount.cmp(a.amount)
-    )
-    // then sort by the type of lock (we try to find relevant)
-    .sort((a, b): number => {
+    .sort((a, b): number => {
+      // First compare by lock type
       if (!a.id.eq(b.id)) {
         for (let i = 0; i < LOCKS_ORDERED.length; i++) {
           const lockName = LOCKS_ORDERED[i];
 
           if (a.id.eq(lockName)) {
             return -1;
           } else if (b.id.eq(lockName)) {
             return 1;
           }
         }
       }
 
-      return 0;
+      // If lock types are equal or not in LOCKS_ORDERED, sort by amount
+      return b.amount.cmp(a.amount);
     })
     .map(({ amount }) => amount);
 
   return sortedLocks?.[0] || allBalances?.lockedBalance;
 }

Line range hint 346-352: Add aria-labels for accessibility.

The vote buttons use icons which should have aria-labels for better accessibility.

-                <CheckIcon sx={{ color: STATUS_COLOR['Confirmed'], fontSize: '28px', stroke: STATUS_COLOR['Confirmed'], strokeWidth: 1.5 }} />
+                <CheckIcon aria-label="Vote Aye" sx={{ color: STATUS_COLOR['Confirmed'], fontSize: '28px', stroke: STATUS_COLOR['Confirmed'], strokeWidth: 1.5 }} />
-                <CloseIcon sx={{ color: 'warning.main', fontSize: '28px', stroke: theme.palette.warning.main, strokeWidth: 1.5 }} />
+                <CloseIcon aria-label="Vote Nay" sx={{ color: 'warning.main', fontSize: '28px', stroke: theme.palette.warning.main, strokeWidth: 1.5 }} />
-                <AbstainIcon sx={{ color: 'primary.light', fontSize: '28px' }} />
+                <AbstainIcon aria-label="Abstain" sx={{ color: 'primary.light', fontSize: '28px' }} />

Line range hint 414-422: Consider using MUI's Grid spacing prop.

Instead of using padding top (pt), consider using MUI's Grid spacing prop for consistent spacing throughout the component.

-            <Grid alignItems='center' container item justifyContent='space-between' sx={{ height: '42px', pt: '25px' }}>
+            <Grid alignItems='center' container item justifyContent='space-between' spacing={3} sx={{ height: '42px' }}>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 9c53680 and d302dc3.

📒 Files selected for processing (5)
  • packages/extension-polkagate/src/components/Convictions.tsx (2 hunks)
  • packages/extension-polkagate/src/fullscreen/governance/post/castVote/Cast.tsx (8 hunks)
  • packages/extension-polkagate/src/hooks/useConvictionOptions.ts (3 hunks)
  • packages/extension-polkagate/src/themes/dark.ts (1 hunks)
  • packages/extension-polkagate/src/themes/light.ts (1 hunks)
🔇 Additional comments (5)
packages/extension-polkagate/src/themes/dark.ts (1)

45-45: LGTM! Good color choice for success states.

The addition of a light variant (#46890C) to the success color palette provides better visual hierarchy while maintaining good contrast ratios.

packages/extension-polkagate/src/hooks/useConvictionOptions.ts (2)

26-28: LGTM! Robust state management implementation.

The state management is well-implemented with:

  • Proper typing for both current and saved options
  • Fallback mechanism using saved options
  • Safe access to API constants using optional chaining

22-22: Verify consistent usage of conviction options.

Let's ensure that all components using this hook handle the undefined return value correctly.

✅ Verification successful

Conviction options are handled correctly across the codebase

The verification shows that useConvictionOptions is used in three components, and all of them handle the undefined return value appropriately:

  • Convictions.tsx: Uses the options directly in a controlled component
  • delegate/Delegate.tsx: Properly handles options alongside conviction state
  • castVote/Cast.tsx: Safely consumes options in the voting flow
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for usages of useConvictionOptions to verify proper handling
rg -A 5 "useConvictionOptions" --type typescript

Length of output: 83


Script:

#!/bin/bash
# Search for usages of useConvictionOptions without file type restriction
rg -A 5 "useConvictionOptions"

Length of output: 6479

packages/extension-polkagate/src/fullscreen/governance/post/castVote/Cast.tsx (2)

91-92: LGTM! Good use of the useInfo hook.

Consolidating API, decimal, formatted, and token access into a single hook improves code organization and reusability.


Line range hint 149-157: Verify conviction initialization logic.

The conviction initialization logic has multiple places where DEFAULT_CONVICTION is set. Consider consolidating this logic to avoid potential race conditions.


/* eslint-disable react/jsx-max-props-per-line */
//@ts-nocheck
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Codebase verification

⚠️ Potential issue

Remove @ts-nocheck directive and fix type issues.

The usage of @ts-nocheck is widespread across the codebase, particularly in the polkagate extension. However, this is not a good practice as it bypasses TypeScript's type checking system. Consider:

  • Fixing the underlying type issues in Cast.tsx
  • Using more specific type annotations where needed
  • Using targeted @ts-ignore comments only for specific lines if absolutely necessary
🔗 Analysis chain

Remove @ts-nocheck directive.

Disabling TypeScript type checking for the entire file is not recommended as it can hide potential type-related issues. Consider fixing the underlying type issues instead.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for other ts-ignore or ts-nocheck directives in the codebase
rg -l "@ts-(ignore|nocheck)" --type ts --type tsx

Length of output: 79


Script:

#!/bin/bash
# Check for other ts-ignore or ts-nocheck directives in the codebase, using ripgrep without file type filters
rg -l "@ts-(ignore|nocheck)"

Length of output: 25166

</Typography>
<Slider
aria-label='Vote Convictions'
defaultValue={conviction || DEFAULT_CONVICTION}
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

Use 'value' instead of 'defaultValue' for the controlled Slider component.

Since conviction is a state managed via setConviction, the Slider should use the value prop instead of defaultValue to ensure it reflects the current state accurately.

Apply this diff:

-          defaultValue={conviction || DEFAULT_CONVICTION}
+          value={conviction || DEFAULT_CONVICTION}
📝 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
defaultValue={conviction || DEFAULT_CONVICTION}
value={conviction || DEFAULT_CONVICTION}

Comment on lines +28 to 30
const onChangeConviction = useCallback((_event: Event, newValue: number | number[], _activeThumb: number): void => {
setConviction(newValue as number);
}, [setConviction]);
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

Ensure 'newValue' is a number before casting in 'onChangeConviction'.

The newValue parameter in onChangeConviction can be a number or an array of numbers. Casting it directly to a number might lead to unexpected behavior if newValue is an array. It's safer to check if newValue is an array and handle it accordingly.

Apply this diff to fix the issue:

 const onChangeConviction = useCallback((_event: Event, newValue: number | number[], _activeThumb: number): void => {
-  setConviction(newValue as number);
+  const value = Array.isArray(newValue) ? newValue[0] : newValue;
+  setConviction(value as number);
 }, [setConviction]);
📝 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
const onChangeConviction = useCallback((_event: Event, newValue: number | number[], _activeThumb: number): void => {
setConviction(newValue as number);
}, [setConviction]);
const onChangeConviction = useCallback((_event: Event, newValue: number | number[], _activeThumb: number): void => {
const value = Array.isArray(newValue) ? newValue[0] : newValue;
setConviction(value as number);
}, [setConviction]);

Comment on lines +55 to +68
useEffect(() => {
// Select all mark labels and apply styles based on slider value
const markLabels = document.querySelectorAll('.MuiSlider-markLabel');

convictionOptions && markLabels.forEach((label, index) => {
const markValue = convictionOptions[index]?.value as number | undefined;

if (markValue && conviction && markValue > conviction) {
(label as HTMLElement).style.color = theme.palette.text.disabled; // Untraversed color
} else {
(label as HTMLElement).style.color = theme.palette.text.primary;
}
});
}, [conviction, convictionOptions, theme]);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid direct DOM manipulation; use Slider's props to style marks dynamically.

Directly manipulating DOM elements with document.querySelectorAll in a React component is discouraged, as it can lead to unexpected side effects and makes the component harder to maintain. Instead, consider using the Slider's props to style the marks based on the conviction value.

Here's how you can refactor the code:

  1. Remove the useEffect that manipulates the DOM.

  2. Modify the marks definition to include dynamic styling:

 const marks = useMemo(() =>
-  convictionOptions?.map(({ value }) => ({ label: `${value} X`, value: value as number }))
+  convictionOptions?.map(({ value }) => ({
+    value: value as number,
+    label: (
+      <span style={{ color: value > (conviction || DEFAULT_CONVICTION) ? theme.palette.text.disabled : theme.palette.text.primary }}>
+        {`${value} X`}
+      </span>
+    )
+  }))
 , [convictionOptions, conviction, theme]);

This way, the mark labels are styled dynamically without manipulating the DOM directly.

Committable suggestion skipped: line range outside the PR's diff.

const match = newText?.match(/\(([^)]+)\)/);
const result = match ? match[1] : '0 days';

return 'Tokens will be locked for ' + result;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Internationalize the entire 'info' string for localization support.

The string 'Tokens will be locked for ' is hard-coded in English. To support localization, the entire string should be wrapped with the translation function.

Apply this diff:

-return 'Tokens will be locked for ' + result;
+return t('Tokens will be locked for {{result}}', { result });

Also, update the dependencies in the useMemo hook:

}, [conviction, convictionOptions]);
+}, [conviction, convictionOptions, t]);

Committable suggestion skipped: line range outside the PR's diff.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Outside diff range and nitpick comments (10)
packages/extension-polkagate/src/fullscreen/partials/ModalTitleWithDrag.tsx (4)

4-4: Remove unnecessary ESLint disable comment.

The ESLint rule disable for jsx-max-props-per-line can be removed by formatting the props across multiple lines where needed.


35-48: Simplify icon rendering logic.

The nested ternary operators and empty fragment make the code harder to read. Consider using early returns or a more straightforward conditional structure.

-        {icon &&
-          <>
-            {isIconVaadin
-              ? <VaadinIcon icon={icon} style={{ color: `${theme.palette.text.primary}`, height: '22px', marginRight: '10px' }} />
-              : isIconFontAwesome
-                ? <FontAwesomeIcon
-                  color={`${theme.palette.text.primary}`}
-                  fontSize='22px'
-                  icon={icon as IconProp}
-                  style={{ marginRight: '10px' }}
-                />
-                : <></>
-            }
-          </>}
+        {isIconVaadin && (
+          <VaadinIcon
+            icon={icon as string}
+            style={{ color: theme.palette.text.primary, height: '22px', marginRight: '10px' }}
+          />
+        )}
+        {isIconFontAwesome && (
+          <FontAwesomeIcon
+            color={theme.palette.text.primary}
+            fontSize='22px'
+            icon={icon as IconProp}
+            style={{ marginRight: '10px' }}
+          />
+        )}

33-68: Use MUI's spacing system consistently.

The component mixes pixel values with MUI's spacing system. Consider using MUI's spacing consistently for better theme integration and responsiveness.

-    <Grid alignItems='center' container justifyContent='space-between' pt='5px'>
+    <Grid alignItems='center' container justifyContent='space-between' pt={0.625}>
       <Grid alignItems='center' container item justifyContent='flex-start' width='fit-content'>
         {/* ... icon rendering ... */}
-        <Typography display='contents' fontSize='22px' fontWeight={700} pl='10px'>
+        <Typography display='contents' fontSize={22} fontWeight={700} pl={1.25}>
           {title}
         </Typography>
       </Grid>
       {/* ... buttons ... */}
-      <Divider sx={{ mt: '5px', width: '100%' }} />
+      <Divider sx={{ mt: 0.625, width: '100%' }} />
     </Grid>

25-70: Consider enhancing accessibility.

The modal title component could benefit from improved accessibility features:

  1. Add appropriate ARIA attributes for the draggable region
  2. Ensure keyboard navigation support for the drag functionality
  3. Add aria-label to the close button

Example improvements:

 <IconButton
+  aria-label="drag modal"
+  role="button"
+  aria-grabbed="false"
   onMouseDown={onMouseDown}
   onMouseMove={onMouseMove}
   onMouseUp={onMouseUp}
 >
   {/* ... */}
 </IconButton>
 <IconButton
+  aria-label="close modal"
   onClick={onClose}
 >
   {/* ... */}
 </IconButton>
packages/extension-polkagate/src/fullscreen/governance/components/DraggableModalWithTitle.tsx (2)

13-22: Consider enhancing the Props interface for better type safety.

The Props interface could be improved to be more specific and maintainable.

Consider this enhancement:

 interface Props {
-  width?: number;
-  maxHeight?: number;
-  minHeight?: number;
+  width?: number | 500;
+  maxHeight?: number | 740;
+  minHeight?: number | 615;
   children: React.ReactElement;
   open: boolean;
-  onClose: () => void
+  onClose: () => void;
   title: string;
-  icon?: string | IconDefinition;
+  icon?: string | IconDefinition | undefined;
 }

66-85: Optimize style object definition.

The style object is recreated on every render. Consider moving static styles outside the component and only compute dynamic values inside.

Consider this optimization:

+const baseStyle = {
+  '&:focus': {
+    outline: 'none'
+  },
+  borderRadius: '10px',
+  boxShadow: 24,
+  maxHeight: `${maxHeight}px`,
+  minHeight: `${minHeight}px`,
+  pb: 3,
+  position: 'absolute' as const,
+  pt: 2,
+  px: 4,
+};

 const style = {
-  '&:focus': {
-    outline: 'none'
-  },
+  ...baseStyle,
   bgcolor: 'background.default',
   border: isDarkMode ? '0.5px solid' : 'none',
   borderColor: 'secondary.light',
-  borderRadius: '10px',
-  boxShadow: 24,
   cursor: isDragging ? 'grabbing' : 'grab',
   left: modalPosition.x,
-  maxHeight: `${maxHeight}px`,
-  minHeight: `${minHeight}px`,
-  pb: 3,
-  position: 'absolute',
-  pt: 2,
-  px: 4,
   top: modalPosition.y,
   width: `${width}px`
 };
packages/extension-polkagate/src/components/Convictions.tsx (1)

39-44: Improve null safety in info calculation.

The current implementation might be vulnerable to undefined values. Consider adding null checks and providing a default value.

 const info = useMemo((): string => {
-  const newText = convictionOptions?.find(({ value }) => value === (conviction || DEFAULT_CONVICTION))?.text;
-  const match = newText?.match(/\(([^)]+)\)/);
+  const newText = convictionOptions?.find(({ value }) => value === (conviction || DEFAULT_CONVICTION))?.text ?? '';
+  const match = newText.match(/\(([^)]+)\)/);

   return match ? match[1] : '0 days';
 }, [conviction, convictionOptions]);
packages/extension-polkagate/src/fullscreen/governance/post/castVote/index.tsx (3)

189-189: Consider adding type safety for the icon prop

The migration to DraggableModalWithTitle improves component reusability. However, consider adding type safety for the icon prop to ensure only valid FontAwesome icons are passed.

- icon={faVoteYea}
+ icon={faVoteYea as IconDefinition}

Also applies to: 275-275


Line range hint 234-244: Remove @ts-ignore and improve type safety

The use of @ts-ignore and type casting suggests potential type safety issues. Consider properly typing the voteInformation prop to avoid runtime errors.

Consider creating a proper type union:

type ReviewVoteInfo = VoteInformation | (typeof votedInfo);

// Then update the Review component prop types:
interface ReviewProps {
  voteInformation: ReviewVoteInfo;
  // ... other props
}

Line range hint 189-275: Consider breaking down the component for better maintainability

The component has grown complex with multiple responsibilities and nested conditions. Consider:

  1. Extracting step-specific components into separate files
  2. Using a step configuration object to map steps to their respective components
  3. Implementing a proper state machine for step transitions

Example refactor approach:

const STEP_COMPONENTS = {
  [STEPS.ABOUT]: About,
  [STEPS.CHECK_SCREEN]: WaitScreen,
  // ... other steps
} as const;

function StepRenderer({ step, props }) {
  const Component = STEP_COMPONENTS[step];
  return Component ? <Component {...props} /> : null;
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between d302dc3 and de597de.

📒 Files selected for processing (4)
  • packages/extension-polkagate/src/components/Convictions.tsx (2 hunks)
  • packages/extension-polkagate/src/fullscreen/governance/components/DraggableModalWithTitle.tsx (1 hunks)
  • packages/extension-polkagate/src/fullscreen/governance/post/castVote/index.tsx (3 hunks)
  • packages/extension-polkagate/src/fullscreen/partials/ModalTitleWithDrag.tsx (1 hunks)
🔇 Additional comments (5)
packages/extension-polkagate/src/components/Convictions.tsx (4)

Line range hint 6-19: LGTM! Well-structured imports and type definitions.

The imports are appropriate for the slider implementation, and the Props interface properly types all required properties. Exporting the DEFAULT_CONVICTION constant is a good practice for maintaining consistency across the application.


28-29: Skip: Array handling issue already reported.

A past review comment already addresses the need to properly handle array values in onChangeConviction.


85-96: LGTM! Well-structured styling implementation.

The styling follows MUI best practices by:

  • Using theme colors for consistent styling
  • Properly targeting specific Slider components
  • Maintaining a clean and maintainable structure

100-109: LGTM! Clean Grid layout implementation.

The Grid layout is well-structured and properly handles the display of locking period information.

packages/extension-polkagate/src/fullscreen/governance/post/castVote/index.tsx (1)

21-21: LGTM: Clean import addition

The new import for DraggableModalWithTitle is properly organized with other component imports.

Comment on lines 16 to 23
interface Props {
onClose: () => void;
title: string;
icon?: string | IconDefinition;
onMouseDown: (e: { clientX: number; clientY: number; }) => void;
onMouseMove?: (e: { clientX: number; clientY: number; }) => void;
onMouseUp?: () => void
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider making all mouse event handlers required for consistent drag behavior.

The onMouseMove and onMouseUp handlers are optional while onMouseDown is required. Since these handlers work together to implement dragging functionality, they should all be required to ensure proper drag behavior implementation.

 interface Props {
   onClose: () => void;
   title: string;
   icon?: string | IconDefinition;
   onMouseDown: (e: { clientX: number; clientY: number; }) => void;
-  onMouseMove?: (e: { clientX: number; clientY: number; }) => void;
-  onMouseUp?: () => void
+  onMouseMove: (e: { clientX: number; clientY: number; }) => void;
+  onMouseUp: () => void;
 }
📝 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
interface Props {
onClose: () => void;
title: string;
icon?: string | IconDefinition;
onMouseDown: (e: { clientX: number; clientY: number; }) => void;
onMouseMove?: (e: { clientX: number; clientY: number; }) => void;
onMouseUp?: () => void
}
interface Props {
onClose: () => void;
title: string;
icon?: string | IconDefinition;
onMouseDown: (e: { clientX: number; clientY: number; }) => void;
onMouseMove: (e: { clientX: number; clientY: number; }) => void;
onMouseUp: () => void;
}

Comment on lines +29 to +31
const isIconVaadin = typeof icon === 'string' && icon?.startsWith('vaadin');
const isIconFontAwesome = !!icon;

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

Fix incorrect icon type check logic.

The isIconFontAwesome check is incorrect as it only verifies if icon exists, not if it's actually a FontAwesome icon. This could lead to runtime errors if an invalid icon type is provided.

 const isIconVaadin = typeof icon === 'string' && icon?.startsWith('vaadin');
-const isIconFontAwesome = !!icon;
+const isIconFontAwesome = typeof icon === 'object' && 'iconName' in icon;
📝 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
const isIconVaadin = typeof icon === 'string' && icon?.startsWith('vaadin');
const isIconFontAwesome = !!icon;
const isIconVaadin = typeof icon === 'string' && icon?.startsWith('vaadin');
const isIconFontAwesome = typeof icon === 'object' && 'iconName' in icon;

Comment on lines +30 to +31
const initialX = (window.innerWidth - width) / 2;
const initialY = (window.innerHeight - maxHeight) / 2;
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

Add window resize handling and SSR safety checks.

The initial position calculation using window.innerWidth/Height has two potential issues:

  1. It doesn't update when the window is resized
  2. It may cause issues during SSR as window is not available

Consider this enhancement:

+const [windowSize, setWindowSize] = useState({ 
+  width: typeof window !== 'undefined' ? window.innerWidth : 0,
+  height: typeof window !== 'undefined' ? window.innerHeight : 0 
+});
+
+useEffect(() => {
+  if (typeof window === 'undefined') return;
+
+  const handleResize = () => {
+    setWindowSize({
+      width: window.innerWidth,
+      height: window.innerHeight
+    });
+  };
+
+  window.addEventListener('resize', handleResize);
+  return () => window.removeEventListener('resize', handleResize);
+}, []);

-const initialX = (window.innerWidth - width) / 2;
-const initialY = (window.innerHeight - maxHeight) / 2;
+const initialX = (windowSize.width - width) / 2;
+const initialY = (windowSize.height - maxHeight) / 2;
📝 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
const initialX = (window.innerWidth - width) / 2;
const initialY = (window.innerHeight - maxHeight) / 2;
const [windowSize, setWindowSize] = useState({
width: typeof window !== 'undefined' ? window.innerWidth : 0,
height: typeof window !== 'undefined' ? window.innerHeight : 0
});
useEffect(() => {
if (typeof window === 'undefined') return;
const handleResize = () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
const initialX = (windowSize.width - width) / 2;
const initialY = (windowSize.height - maxHeight) / 2;

Comment on lines 87 to 105
return (
<Modal onClose={_onClose} open={open}>
<Box
onMouseLeave={handleMouseUp}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
sx={{ ...style }}

>
<ModalTitleWithDrag
icon={icon}
onClose={onClose}
onMouseDown={handleMouseDown}
title= {title}
/>
{children}
</Box>
</Modal>
);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance modal accessibility.

The modal could benefit from improved accessibility features.

Consider these accessibility improvements:

 <Modal
+  aria-describedby="modal-description"
+  aria-labelledby="modal-title"
   onClose={_onClose}
   open={open}
 >
   <Box
+    role="dialog"
+    tabIndex={-1}
     onMouseLeave={handleMouseUp}
     onMouseMove={handleMouseMove}
     onMouseUp={handleMouseUp}
     sx={{ ...style }}
   >
     <ModalTitleWithDrag
+      id="modal-title"
       icon={icon}
       onClose={onClose}
       onMouseDown={handleMouseDown}
       title={title}
     />
-    {children}
+    <div id="modal-description">
+      {children}
+    </div>
   </Box>
 </Modal>
📝 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
return (
<Modal onClose={_onClose} open={open}>
<Box
onMouseLeave={handleMouseUp}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
sx={{ ...style }}
>
<ModalTitleWithDrag
icon={icon}
onClose={onClose}
onMouseDown={handleMouseDown}
title= {title}
/>
{children}
</Box>
</Modal>
);
return (
<Modal
aria-describedby="modal-description"
aria-labelledby="modal-title"
onClose={_onClose}
open={open}
>
<Box
role="dialog"
tabIndex={-1}
onMouseLeave={handleMouseUp}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
sx={{ ...style }}
>
<ModalTitleWithDrag
id="modal-title"
icon={icon}
onClose={onClose}
onMouseDown={handleMouseDown}
title={title}
/>
<div id="modal-description">
{children}
</div>
</Box>
</Modal>
);

Comment on lines +49 to +60
const handleMouseMove = useCallback((e: { clientX: number; clientY: number; }) => {
if (isDragging) {
const dx = e.clientX - dragStartPosition.x;
const dy = e.clientY - dragStartPosition.y;

setModalPosition((prevPosition) => ({
x: prevPosition.x + dx,
y: prevPosition.y + dy
}));
setDragStartPosition({ x: e.clientX, y: e.clientY });
}
}, [dragStartPosition, isDragging]);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add bounds checking and cleanup for mouse events.

The drag handling could be improved by:

  1. Preventing the modal from being dragged outside viewport bounds
  2. Cleaning up mouse events on component unmount

Consider these improvements:

 const handleMouseMove = useCallback((e: { clientX: number; clientY: number; }) => {
   if (isDragging) {
     const dx = e.clientX - dragStartPosition.x;
     const dy = e.clientY - dragStartPosition.y;

+    const newX = Math.max(0, Math.min(window.innerWidth - width, prevPosition.x + dx));
+    const newY = Math.max(0, Math.min(window.innerHeight - maxHeight, prevPosition.y + dy));
+
     setModalPosition((prevPosition) => ({
-      x: prevPosition.x + dx,
-      y: prevPosition.y + dy
+      x: newX,
+      y: newY
     }));
     setDragStartPosition({ x: e.clientX, y: e.clientY });
   }
 }, [dragStartPosition, isDragging]);

+useEffect(() => {
+  document.addEventListener('mousemove', handleMouseMove);
+  document.addEventListener('mouseup', handleMouseUp);
+
+  return () => {
+    document.removeEventListener('mousemove', handleMouseMove);
+    document.removeEventListener('mouseup', handleMouseUp);
+  };
+}, [handleMouseMove, handleMouseUp]);

Committable suggestion skipped: line range outside the PR's diff.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (3)
packages/extension-polkagate/src/components/Convictions.tsx (2)

39-44: Consider improving the info text extraction logic.

The current regex-based text extraction is fragile. Consider having the convictionOptions hook return structured data instead.

-  const info = useMemo((): string => {
-    const newText = convictionOptions?.find(({ value }) => value === (conviction || DEFAULT_CONVICTION))?.text;
-    const match = newText?.match(/\(([^)]+)\)/);
-
-    return match ? match[1] : '0 days';
+  const info = useMemo((): string => {
+    const option = convictionOptions?.find(({ value }) => value === (conviction || DEFAULT_CONVICTION));
+    return option?.lockPeriod || t('0 days');
   }, [conviction, convictionOptions]);

100-109: Improve translation implementation for the lock period display.

The current implementation concatenates translated and non-translated parts. Consider using translation variables for better maintainability.

-          <Typography sx={{ fontSize: '14px' }}>
-            {t('Tokens will be locked for')}
-          </Typography>
-        </Grid>
-        <Grid item sx={{ fontSize: '16px', fontWeight: 400 }}>
-          {info}
+          <Typography sx={{ fontSize: '14px' }}>
+            {t('Tokens will be locked for {{period}}', { period: info })}
+          </Typography>
packages/extension-polkagate/src/fullscreen/governance/delegate/index.tsx (1)

Line range hint 144-186: Consider extracting title logic to a separate hook

The title computation logic in the useMemo hook is complex and handles multiple states. Consider extracting it to a custom hook (e.g., useDelegateTitle) for better maintainability and reusability.

Here's a suggested refactor:

const useDelegateTitle = (step: number, status: DelegationStatus, txInfo?: TxInfo) => {
  const { t } = useTranslation();
  
  return useMemo(() => {
    switch (step) {
      case STEPS.ABOUT:
        return t('Delegate Vote');
      case STEPS.CHECK_SCREEN:
        return t('Delegation status');
      case STEPS.INDEX:
      case STEPS.CHOOSE_DELEGATOR:
        return t('Delegate Vote ({{ step }}/3)', { 
          replace: { step: step === STEPS.INDEX ? 1 : 2 } 
        });
      // ... rest of the cases
      default:
        return '';
    }
  }, [status, step, t, txInfo?.success]);
};
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between de597de and 9452d56.

📒 Files selected for processing (4)
  • packages/extension-polkagate/src/components/Convictions.tsx (2 hunks)
  • packages/extension-polkagate/src/fullscreen/governance/components/DraggableModalWithTitle.tsx (1 hunks)
  • packages/extension-polkagate/src/fullscreen/governance/delegate/index.tsx (3 hunks)
  • packages/extension-polkagate/src/fullscreen/partials/ModalTitleWithDrag.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/extension-polkagate/src/fullscreen/governance/components/DraggableModalWithTitle.tsx
  • packages/extension-polkagate/src/fullscreen/partials/ModalTitleWithDrag.tsx
🔇 Additional comments (5)
packages/extension-polkagate/src/components/Convictions.tsx (3)

6-7: LGTM: Imports and constant definitions are well-structured.

The imports are appropriate for the slider implementation, and the DEFAULT_CONVICTION constant is properly exported.

Also applies to: 19-20


32-37: LGTM: Clean implementation of min/max calculation.

The useMemo hook efficiently calculates min/max values with proper type assertions and fallback values.


85-96: LGTM: Well-structured slider styling.

The custom styling is cleanly implemented using MUI's sx prop with proper color theming.

packages/extension-polkagate/src/fullscreen/governance/delegate/index.tsx (2)

23-23: LGTM: Clean import addition

The new import for DraggableModalWithTitle is properly organized with other component imports.


288-293: Verify icon consistency across steps

The modal implementation looks good with proper prop handling. However, the icon selection logic uses a mix of Font Awesome and Vaadin icons. Consider standardizing the icon library usage for better maintainability.

Let's check for icon consistency across the codebase:

✅ Verification successful

Based on the search results, I can now generate the final response since I have enough information about icon usage patterns in the codebase:

Icon usage is consistent within component contexts

The codebase shows a clear pattern of using specific icon libraries for specific purposes:

  • Font Awesome icons are used for action/functional buttons and status indicators
  • Vaadin icons are used primarily for navigation, menus and modal headers

The mixed usage in the delegate modal is actually consistent with this pattern - faUserAstronaut for the proxy step (functional) and vaadin:money-withdraw for the transaction step (modal header).

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for mixed icon usage patterns
# Look for Font Awesome and Vaadin icon imports/usage
rg -n "fa[A-Z]|'vaadin:" packages/extension-polkagate/src/

Length of output: 77456

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants