Skip to content

Conversation

@kemuru
Copy link
Contributor

@kemuru kemuru commented Jan 31, 2025

PR-Codex overview

This PR focuses on refactoring the Profile and JurorInfo components, updating routes, and enhancing the user interface for better navigation and display of juror-related information. It also introduces new components and modifies existing ones for improved functionality.

Detailed summary

  • Deleted web/src/pages/Profile/JurorInfo/index.tsx and web/src/utils/userLevelCalculation.ts.
  • Updated routing in web/src/app.tsx to use wildcard paths.
  • Modified links in web/src/pages/Jurors/index.tsx and web/src/layout/Header/navbar/Menu/Settings/General/WalletAndProfile.tsx to point to /profile/stakes/1.
  • Adjusted padding styles in web/src/pages/Courts/CourtDetails/JurorsStakedByCourt/DisplayJurors/Header.tsx.
  • Created a new Header component in web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/Header.tsx.
  • Changed import paths for PixelArt and Coherence components in web/src/pages/Home/TopJurors/JurorCard/JurorLevel.tsx and web/src/pages/Profile/JurorInfo/Coherence.tsx.
  • Added several new components like Vote, Round, CaseNumber, and Stats with respective styling and functionality.
  • Refactored JurorCard and BottomContent components to improve layout and data handling.
  • Updated StakingHistory and CurrentStakes components to enhance data presentation and user interaction.
  • Implemented new utility functions for handling court names and voting details.
  • Enhanced overall styling and responsiveness across various components.

The following files were skipped due to too many changes: web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/StakeEventCard/DesktopCard.tsx, web/src/pages/Profile/JurorInfo/Header.tsx, web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/index.tsx, web/src/pages/Profile/index.tsx

✨ Ask PR-Codex anything about this PR by commenting with /codex {your question}

Summary by CodeRabbit

  • New Features

    • Tabbed navigation on profile for easier access to Stakes, Votes, Cases
    • Staking history with pagination, search and per-court view
    • Votes page with stats, filters and sorting
    • Enhanced juror links: optional external explorer target and clearer link behavior
    • New stake and staking-event displays (desktop + mobile)
  • Refactor

    • Reorganized profile and juror card layout into Top/Bottom content and new subviews

@kemuru kemuru changed the title feat: setup new profile page structure, new juror card style, add tab… feat: new profile page Jan 31, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 31, 2025

Walkthrough

Refactors juror link/title into a configurable JurorLink, restructures the Profile into tabbed routes (Stakes, Votes, Cases), adds staking history and related court-stake components/hooks, and updates numerous imports, paths, and small UI/style adjustments across the web app.

Changes

Cohort / File(s) Change Summary
JurorLink component & usages
web/src/components/JurorLink.tsx, web/src/pages/Cases/.../AccordionTitle.tsx, web/src/pages/Home/TopJurors/JurorCard/DesktopCard.tsx, web/src/pages/Home/TopJurors/JurorCard/MobileCard.tsx, web/src/pages/Courts/CourtDetails/.../JurorCard.tsx, web/src/pages/Profile/JurorCard/TopContent/index.tsx, web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/.../DesktopCard.tsx, web/src/pages/Courts/.../StakeEventCard/.../MobileCard.tsx
Renamed IJurorTitleIJurorLink, component JurorTitleJurorLink, added isInternalLink prop, split internal profile vs external explorer link (memoized explorer URL), conditional target/rel and icon (ArrowIcon / NewTabIcon). Replaced usages across multiple components.
Profile tabs & routing
web/src/pages/Profile/index.tsx, web/src/app.tsx
Replaced previous Profile routing with wildcard profile/* and implemented tabbed navigation using TabsComponent, Routes, and utilities to map tabs to routes.
Stakes area refactor
web/src/pages/Profile/Stakes/index.tsx, web/src/pages/Profile/Stakes/CurrentStakes/*, web/src/pages/Profile/Stakes/CourtCard/*, web/src/pages/Profile/Stakes/StakingHistory.tsx
Reworked Stakes to accept searchParamAddress, split into CurrentStakes and StakingHistory, introduced CourtCard, Header (total/locked), exported CourtCardsContainer, updated stake data wiring and layouts.
Votes & Cases views
web/src/pages/Profile/Votes/*, web/src/pages/Profile/Cases/index.tsx
Added Votes view, StatsAndFilters (Stats + Filters), and VoteCard components (CourtName, CaseNumber, Vote, Round, CaseStatus). Extracted Cases component for profile cases listing with pagination.
JurorCard restructuring & Bottom/Top content
web/src/pages/Profile/JurorCard/index.tsx, web/src/pages/Profile/JurorCard/Header.tsx, web/src/pages/Profile/JurorCard/BottomContent/*, web/src/pages/Profile/JurorCard/TopContent/index.tsx
Renamed JurorInfoJurorCard, addressToQuerysearchParamAddress; replaced monolithic layout with TopContent and BottomContent, removed address copy/explorer UI from header, adjusted paddings/gaps and component composition.
Staking history by court (new feature)
web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/*, web/src/pages/Courts/CourtDetails/index.tsx
Added StakingHistoryByCourt area with Search, DisplayStakes (infinite/paginated load), StakeEventCard (mobile/desktop), Desktop/Mobile headers, integrated into court details via StakingSections.
New hooks & utils
web/src/hooks/useStakingHistory.ts, web/src/hooks/useStakingEventsByCourt.ts, web/src/hooks/queries/useStakingHistory.ts, web/src/utils/findCourtNameById.ts
Added hooks for staking history and events (GraphQL/react-query), and a recursive findCourtNameById utility.
Exports & types visibility
web/src/utils/userLevelCalculation.ts, web/src/components/DisputeView/PeriodBanner.tsx
Exported ILevelCriteria and made getPeriodColors exported for external use.
Profile link path updates
web/src/components/EvidenceCard.tsx, web/src/layout/Header/.../WalletAndProfile.tsx, web/src/pages/Jurors/index.tsx
Updated profile links from /profile/1/desc/all?address=.../profile/stakes/1?address=....
Import path & minor UI/styling adjustments
web/src/components/Popup/MiniGuides/JurorLevels.tsx, web/src/pages/Home/TopJurors/JurorCard/JurorLevel.tsx, web/src/pages/Profile/JurorCard/BottomContent/*, various court details files, contracts/audit/METRICS.md
Adjusted several import paths (moved PixelArt/Coherence), added/updated styled containers (TokenRewardsContainer, PixelArt centering), minor padding/margin tweaks, and a small MD formatting change.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User
    participant Router as App Router
    participant Profile as Profile Tabs
    participant Stakes as Stakes View
    participant History as StakingHistory/DisplayStakes
    participant Current as CurrentStakes
    participant JurorLink as JurorLink Component
    User->>Router: Navigate to /profile/stakes?address=0x...
    Router->>Profile: Route to Profile (wildcard)
    Profile->>Stakes: Activate Stakes tab (route)
    Stakes->>Current: Render CurrentStakes
    Current->>Current: fetch juror stake data (query)
    Current->>JurorLink: render juror address (isInternalLink=true)
    JurorLink->>JurorLink: choose internal URL -> render ArrowIcon
    User->>History: Scroll / open StakingHistory
    History->>History: fetch staking events (useStakingEventsByCourt / pagination)
    History->>StakeEventCard: render event cards (desktop/mobile)
    StakeEventCard->>JurorLink: render juror address (isInternalLink may be false)
    JurorLink->>JurorLink: if external -> set target/_blank and NewTabIcon
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas needing extra attention:

  • JurorLink linking logic and correct use of DEFAULT_CHAIN/getChain and memoization.
  • Profile tab routing (wildcard route) and tab <-> URL synchronization (handleTabChange / getTabIndex).
  • Stakes/StakingHistory pagination, accumulation, and intersection-observer / infinite-load behavior.
  • New hooks (useStakingEventsByCourt / useStakingHistory) — GraphQL query correctness and enabled conditions.
  • Renames/prop changes (addressToQuerysearchParamAddress, JurorInfoJurorCard) — ensure all usages updated and types aligned.

Possibly related PRs

Suggested labels

Type: Feature🗿, Package: Web, Type: UX

Suggested reviewers

  • alcercu
  • jaybuidl

Poem

🐰 I hopped through links both near and far,
I braided tabs and fixed the navbar,
Stakes and votes now sit in rows,
Explorer icons wink and glow.
Happy refactor — from your rabbit, ta-da! 🥕✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: new profile page' directly addresses the main objective of restructuring and refactoring the Profile component with new design, which aligns with the substantial changes across profile-related files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat(web)/profile-page-new-design

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e6b8207 and e67c533.

📒 Files selected for processing (1)
  • web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/StakeEventCard/DesktopCard.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/StakeEventCard/DesktopCard.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: Redirect rules - kleros-v2-neo
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Redirect rules - kleros-v2-testnet-devtools
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-neo
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet-devtools
  • GitHub Check: Pages changed - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-neo
  • GitHub Check: Pages changed - kleros-v2-testnet-devtools

Comment @coderabbitai help to get the list of available commands and usage tips.

@netlify
Copy link

netlify bot commented Jan 31, 2025

Deploy Preview for kleros-v2-university failed. Why did it fail? →

Name Link
🔨 Latest commit 89fa758
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-university/deploys/68a63e766b17a00008dab258

@netlify
Copy link

netlify bot commented Jan 31, 2025

Deploy Preview for kleros-v2-neo ready!

Name Link
🔨 Latest commit e67c533
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-neo/deploys/69146147d70e8e0008019369
😎 Deploy Preview https://deploy-preview-1871--kleros-v2-neo.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link

netlify bot commented Jan 31, 2025

Deploy Preview for kleros-v2-testnet ready!

Name Link
🔨 Latest commit e67c533
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-testnet/deploys/69146147ab54b60008e44023
😎 Deploy Preview https://deploy-preview-1871--kleros-v2-testnet.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link

netlify bot commented Jan 31, 2025

Deploy Preview for kleros-v2-testnet-devtools failed. Why did it fail? →

Name Link
🔨 Latest commit e67c533
🔍 Latest deploy log https://app.netlify.com/projects/kleros-v2-testnet-devtools/deploys/691461472f37d2000815a9a1

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: 3

🧹 Nitpick comments (12)
web/src/pages/Profile/JurorInfo/BottomContent/Coherence.tsx (1)

31-31: Consider utilizing additional properties from ILevelCriteria.

The userLevelData now has access to additional properties like minDisputes, minCoherencePercentage, and maxCoherencePercentage. Consider displaying these values to provide users with more context about their current level and what they need to achieve for the next level.

Example enhancement:

 <Container>
   <small>{userLevelData.title}</small>
   <label>Level {userLevelData.level}</label>
+  <small>Min. Disputes: {userLevelData.minDisputes}</small>
+  <small>Required Coherence: {userLevelData.minCoherencePercentage}%-{userLevelData.maxCoherencePercentage}%</small>
   <CircularProgress
     progress={parseFloat(((totalCoherentVotes / Math.max(totalResolvedVotes, 1)) * 100).toFixed(2))}
   />
web/src/pages/Profile/index.tsx (2)

89-96: Consider memoizing the tab callback function.

The tab callback function is recreated on every render. Consider memoizing it with useCallback:

- const [currentTab, setCurrentTab] = useState(0);
+ const [currentTab, setCurrentTab] = useState(0);
+ const handleTabChange = useCallback((n: number) => setCurrentTab(n), []);

Then update the callback prop:

- <StyledTabs currentValue={currentTab} items={TABS} callback={(n) => setCurrentTab(n)} />
+ <StyledTabs currentValue={currentTab} items={TABS} callback={handleTabChange} />

99-110: Consider breaking down props for better maintainability.

The StyledCasesDisplay component receives many props, some through object spreading. For better maintainability and type safety, consider explicitly listing all props:

<StyledCasesDisplay
  title="Cases Drawn"
  disputes={userData?.user !== null ? (disputesData?.user?.disputes as DisputeDetailsFragment[]) : []}
  numberDisputes={totalCases}
  numberClosedDisputes={totalResolvedCases}
  totalPages={totalPages}
  currentPage={pageNumber}
  setCurrentPage={(newPage: number) =>
    navigate(`${location}/${newPage}/${order}/${filter}?${searchParams.toString()}`)
  }
- {...{ casesPerPage }}
+ casesPerPage={casesPerPage}
/>
web/src/pages/Profile/Stakes/index.tsx (1)

Line range hint 54-63: Refactor duplicate filter condition and improve type safety.

  1. The filter condition staked > 0 is duplicated in the code.
  2. The court name nullish coalescing could be handled more elegantly with proper typing.

Consider this improvement:

- {!isStaked && !isLoading ? <StyledLabel>No stakes found</StyledLabel> : null}
- {isStaked && !isLoading ? (
-   <CourtCardsContainer>
-     {stakeData?.jurorTokensPerCourts
-       ?.filter(({ staked }) => staked > 0)
-       .map(({ court: { id, name }, staked }) => (
-         <CourtCard key={id} name={name ?? ""} stake={staked} {...{ id }} />
-       ))}
-   </CourtCardsContainer>
- ) : null}
+ {!isLoading && (
+   <>
+     {!isStaked ? (
+       <StyledLabel>No stakes found</StyledLabel>
+     ) : (
+       <CourtCardsContainer>
+         {stakedCourts?.map(({ court: { id, name }, staked }) => (
+           <CourtCard
+             key={id}
+             name={name ?? id.toString()}
+             stake={staked}
+             {...{ id }}
+           />
+         ))}
+       </CourtCardsContainer>
+     )}
+   </>
+ )}

This refactor:

  1. Reuses the filtered stakedCourts array
  2. Provides a more meaningful fallback for missing court names
  3. Simplifies the conditional rendering logic
web/src/pages/Profile/JurorInfo/TopContent/index.tsx (1)

14-16: Add accessibility attributes to label.

The StyledLabel component should include appropriate ARIA attributes for better accessibility.

const StyledLabel = styled.label`
  font-size: 14px;
+ &[aria-label] {
+   cursor: default;
+ }
`;

Usage:

- <StyledLabel>
+ <StyledLabel aria-label="Total resolved disputes">
web/src/pages/Profile/JurorInfo/BottomContent/PixelArt.tsx (1)

Line range hint 50-60: Enhance image handling and accessibility.

The component could benefit from better error handling and accessibility improvements:

  1. Add error handling for image loading failures
  2. Make alt text more descriptive and customizable
interface IPixelArt {
  level: number;
  width: number | string;
  height: number | string;
+ altText?: string;
}

const PixelArt: React.FC<IPixelArt> = ({ 
  level, 
  width, 
  height,
+ altText = `Level ${level} Juror Pixel Art`,
}) => {
  const [imageLoaded, setImageLoaded] = useState(false);
+ const [error, setError] = useState(false);

  return (
    <Container>
-     {!imageLoaded && <StyledSkeleton width={width} height={height} />}
+     {!imageLoaded && !error && <StyledSkeleton width={width} height={height} />}
      <StyledImage
        src={images[level]}
-       alt="Pixel Art per Level"
+       alt={altText}
        onLoad={() => setImageLoaded(true)}
+       onError={() => setError(true)}
        show={imageLoaded}
        width={width}
        height={height}
      />
+     {error && <div>Failed to load image</div>}
    </Container>
  );
};
web/src/pages/Profile/JurorInfo/BottomContent/index.tsx (1)

45-50: Consider adding prop types for child components.

The interface IBottomContent could be more explicit about the required props for child components.

+ interface ICoherenceProps {
+   isMiniGuide: boolean;
+   userLevelData: ILevelCriteria;
+   totalCoherentVotes: number;
+   totalResolvedVotes: number;
+ }

+ interface IJurorRewardsProps {
+   addressToQuery: `0x${string}`;
+ }

interface IBottomContent {
  userLevelData: ILevelCriteria;
  totalCoherentVotes: number;
  totalResolvedVotes: number;
  addressToQuery: `0x${string}`;
+ coherenceProps?: Partial<ICoherenceProps>;
+ jurorRewardsProps?: Partial<IJurorRewardsProps>;
}
web/src/pages/Home/TopJurors/JurorCard/DesktopCard.tsx (1)

Line range hint 37-41: Update address prop type for consistency.

The address prop type should match the hexadecimal format used in other components.

interface IDesktopCard {
  rank?: number;
- address: string;
+ address: `0x${string}`;
  totalCoherentVotes: string;
  totalResolvedVotes: string;
  totalResolvedDisputes: string;
}
web/src/pages/Profile/JurorInfo/StakingRewards.tsx (1)

9-9: Consider removing commented code.

The commented code includes TokenRewards import and several UI components that are no longer in use. If these components are truly deprecated, they should be removed rather than left as comments.

-// import TokenRewards from "./TokenRewards";
-    // <Container>
-    //   <WithHelpTooltip place="bottom" {...{ tooltipMsg }}>
-    //     <label>
-    //       Staking Rewards: <small>APY 6%</small>
-    //     </label>
-    //     Coming soon
-    //   </WithHelpTooltip>
-    //   <TokenRewards token="PNK" amount="10,000" value="8,783" />
-    //   <ClaimPNK />
-    // </Container>

Also applies to: 55-64

web/src/components/JurorLink.tsx (1)

54-56: Consider memoizing the chain data.

The getChain function is called on every render. Consider memoizing the chain data separately to optimize performance.

+ const chain = useMemo(() => getChain(DEFAULT_CHAIN), []);
  const addressExplorerLink = useMemo(() => {
-   return `${getChain(DEFAULT_CHAIN)?.blockExplorers?.default.url}/address/${address}`;
+   return `${chain?.blockExplorers?.default.url}/address/${address}`;
  }, [address]);
web/src/pages/Profile/JurorInfo/BottomContent/JurorRewards.tsx (1)

18-18: Consider simplifying responsive styles.

The Container uses a landscape style for alignment. Consider using a media query or flex-wrap for simpler responsive behavior.

const Container = styled.div`
  display: flex;
  flex-direction: column;
  align-items: center;
  width: auto;
  gap: 24px;

- ${landscapeStyle(
-   () => css`
-     align-items: flex-start;
-   `
- )}
+ @media (orientation: landscape) {
+   align-items: flex-start;
+ }
`;

Also applies to: 20-27

web/src/pages/Profile/JurorInfo/Header.tsx (1)

76-76: LGTM! Consider dynamic title.

The header simplification aligns well with the profile page restructuring. The removal of redundant address display improves component focus.

Consider making the title dynamic to show the juror's level:

-      <StyledTitle>Juror Profile</StyledTitle>
+      <StyledTitle>Level {levelNumber} Juror Profile</StyledTitle>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2730163 and 45bc1fb.

📒 Files selected for processing (18)
  • web/src/components/JurorLink.tsx (2 hunks)
  • web/src/components/Popup/MiniGuides/JurorLevels.tsx (1 hunks)
  • web/src/pages/Cases/CaseDetails/Voting/VotesDetails/AccordionTitle.tsx (2 hunks)
  • web/src/pages/Home/TopJurors/JurorCard/DesktopCard.tsx (2 hunks)
  • web/src/pages/Home/TopJurors/JurorCard/JurorLevel.tsx (1 hunks)
  • web/src/pages/Home/TopJurors/JurorCard/MobileCard.tsx (2 hunks)
  • web/src/pages/Profile/JurorInfo/BottomContent/Coherence.tsx (2 hunks)
  • web/src/pages/Profile/JurorInfo/BottomContent/JurorRewards.tsx (3 hunks)
  • web/src/pages/Profile/JurorInfo/BottomContent/PixelArt.tsx (3 hunks)
  • web/src/pages/Profile/JurorInfo/BottomContent/index.tsx (1 hunks)
  • web/src/pages/Profile/JurorInfo/Header.tsx (2 hunks)
  • web/src/pages/Profile/JurorInfo/StakingRewards.tsx (2 hunks)
  • web/src/pages/Profile/JurorInfo/TopContent/index.tsx (1 hunks)
  • web/src/pages/Profile/JurorInfo/index.tsx (2 hunks)
  • web/src/pages/Profile/Stakes/Header.tsx (1 hunks)
  • web/src/pages/Profile/Stakes/index.tsx (4 hunks)
  • web/src/pages/Profile/index.tsx (4 hunks)
  • web/src/utils/userLevelCalculation.ts (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • web/src/pages/Home/TopJurors/JurorCard/JurorLevel.tsx
  • web/src/components/Popup/MiniGuides/JurorLevels.tsx
⏰ Context from checks skipped due to timeout of 90000ms (16)
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet
  • GitHub Check: Redirect rules - kleros-v2-neo
  • GitHub Check: Redirect rules - kleros-v2-university
  • GitHub Check: Header rules - kleros-v2-neo
  • GitHub Check: Header rules - kleros-v2-university
  • GitHub Check: Pages changed - kleros-v2-neo
  • GitHub Check: Pages changed - kleros-v2-university
  • GitHub Check: contracts-testing
  • GitHub Check: Analyze (javascript)
  • GitHub Check: SonarCloud
  • GitHub Check: Mend Security Check
🔇 Additional comments (19)
web/src/utils/userLevelCalculation.ts (1)

1-1: LGTM! Good improvement in type visibility.

Making the ILevelCriteria interface explicitly exported improves code organization and type safety across the codebase.

web/src/pages/Profile/JurorInfo/BottomContent/Coherence.tsx (1)

4-8: LGTM! Import changes look good.

The imports are properly organized and include the necessary dependencies.

web/src/pages/Profile/index.tsx (2)

38-48: LGTM! Styling changes follow responsive design patterns.

The margin adjustments and new StyledTabs component are well-implemented, maintaining consistency with the project's responsive design approach.


58-62: Verify the implementation plan for the Votes tab.

The Votes tab is currently a placeholder rendering null. Consider either:

  1. Adding a TODO comment to track this pending implementation
  2. Adding a "Coming Soon" message for better UX
  3. Removing the tab if it's not planned for this PR

Also applies to: 112-112

web/src/pages/Profile/Stakes/Header.tsx (1)

63-63: LGTM! Simplified title improves code clarity.

The static title "Stakes" aligns well with the new profile page structure and tab-based navigation mentioned in the PR objectives.

web/src/pages/Profile/Stakes/index.tsx (3)

17-17: LGTM! Adjusted margin improves visual spacing.

The updated margin provides better vertical rhythm in the layout.


37-41: LGTM! Improved semantic naming.

Renaming from Courts to Stakes better reflects the component's purpose and aligns with the new profile page structure.


68-68: LGTM! Updated export matches new component name.

web/src/pages/Home/TopJurors/JurorCard/DesktopCard.tsx (1)

60-60: Add isInternalLink prop to JurorLink.

The JurorLink component should specify whether it's an internal or external link.

- <JurorLink address={address} />
+ <JurorLink address={address} isInternalLink={true} />

Let's verify the JurorLink component's usage across the codebase:

web/src/pages/Profile/JurorInfo/StakingRewards.tsx (2)

14-15: LGTM! Improved layout spacing.

The container's alignment and gap properties have been updated to provide better visual spacing and centering.


50-51: Verify if "Coming soon" is the intended placeholder.

The component has been simplified to show only a "Coming soon" message. Consider:

  1. Adding a more descriptive message about when this feature will be available
  2. Including a progress indicator or estimated timeline
  3. Adding a link to the KIP for more information

Also applies to: 67-69

web/src/components/JurorLink.tsx (2)

43-45: LGTM! Clear interface definition.

The interface has been renamed to match the component and includes an optional boolean prop for controlling link behavior.


61-67: LGTM! Secure external link handling.

The component correctly implements security best practices for external links by adding noopener noreferrer and _blank target attributes.

web/src/pages/Profile/JurorInfo/index.tsx (3)

15-17: LGTM! Improved component organization.

The imports reflect a better separation of concerns with distinct TopContent and BottomContent components.


26-26: LGTM! Consistent spacing.

The Card's gap and padding have been adjusted to provide more consistent spacing throughout the component.

Also applies to: 29-29


52-54: Verify prop spreading.

The component spreads multiple props to BottomContent. Consider explicitly listing the required props to improve maintainability and type safety.

- <BottomContent {...{ userLevelData, totalCoherentVotes, totalResolvedVotes, addressToQuery }} />
+ <BottomContent
+   userLevelData={userLevelData}
+   totalCoherentVotes={totalCoherentVotes}
+   totalResolvedVotes={totalResolvedVotes}
+   addressToQuery={addressToQuery}
+ />
web/src/pages/Profile/JurorInfo/BottomContent/JurorRewards.tsx (1)

29-33: LGTM! Improved token rewards layout.

The new TokenRewardsContainer provides better organization for multiple token rewards with consistent spacing.

Also applies to: 58-62

web/src/pages/Cases/CaseDetails/Voting/VotesDetails/AccordionTitle.tsx (1)

11-11: LGTM! Verify link structure consistency.

The replacement of JurorTitle with JurorLink is consistent with the profile page restructuring. The component is properly wrapped in StyledInternalLink with the correct profile path.

Let's verify the link structure consistency across the codebase:

Also applies to: 95-95

✅ Verification successful

Link structure verified - consistent implementation

All profile links in the codebase follow the uniform pattern /profile/1/desc/all?address=${address}, maintaining consistency across components.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for consistent profile link structure across the codebase
# Expected pattern: /profile/1/desc/all?address=<address>

rg -g '*.{ts,tsx}' '/profile/\d+/\w+/\w+\?address='

Length of output: 368

web/src/pages/Home/TopJurors/JurorCard/MobileCard.tsx (1)

14-14: Verify link behavior consistency.

While the replacement of JurorTitle with JurorLink is correct, the implementation differs from AccordionTitle.tsx where JurorLink is wrapped in StyledInternalLink.

Let's verify the link behavior consistency:

Also applies to: 100-100

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: 1

🧹 Nitpick comments (5)
web/src/pages/Profile/JurorCard/BottomContent/index.tsx (1)

32-43: Remove redundant flex-direction property.

The flex-direction property is declared twice in the LeftContent styled component. The first declaration at line 34 is overridden by the second declaration at line 36.

const LeftContent = styled.div`
  display: flex;
- flex-direction: row;
  gap: 48px;
  flex-direction: column;

  ${landscapeStyle(
    () => css`
      flex-direction: row;
    `
  )}
`;
web/src/pages/Profile/JurorCard/StakingRewards.tsx (2)

55-64: Remove or document commented code.

There's a significant block of commented-out code that includes functionality for displaying staking rewards, token rewards, and claim functionality. If this code will be used in the future, consider:

  1. Adding a TODO comment explaining when it will be implemented
  2. Moving it to a separate branch until ready
  3. Creating an issue to track its implementation

66-70: Consider adding a more informative placeholder.

The current "Coming soon" message could be more informative. Consider adding:

  • Expected release timeframe
  • Link to the KIP mentioned in the tooltip
    <Container>
      <WithHelpTooltip place="bottom" {...{ tooltipMsg }}>
        <label>Staking Rewards</label>
      </WithHelpTooltip>
-      <label>Coming soon</label>
+      <label>Coming soon - Pending KIP approval</label>
    </Container>
web/src/pages/Profile/Cases/index.tsx (1)

33-36: Consider making casesPerPage configurable.

The hardcoded value of 3 for casesPerPage might be better as a prop or configuration value for flexibility.

-  const casesPerPage = 3;
+  const casesPerPage = props.casesPerPage ?? 3;
web/src/pages/Profile/index.tsx (1)

91-101: Consider adding loading states for route transitions.

While the routing implementation is good, consider adding loading states or suspense boundaries for a better user experience during route transitions.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 45bc1fb and c3213ca.

⛔ Files ignored due to path filters (1)
  • web/src/assets/svgs/icons/voted-ballot.svg is excluded by !**/*.svg
📒 Files selected for processing (20)
  • web/src/app.tsx (1 hunks)
  • web/src/components/EvidenceCard.tsx (1 hunks)
  • web/src/components/JurorLink.tsx (2 hunks)
  • web/src/components/Popup/MiniGuides/JurorLevels.tsx (1 hunks)
  • web/src/layout/Header/navbar/Menu/Settings/General/WalletAndProfile.tsx (1 hunks)
  • web/src/pages/Cases/CaseDetails/Voting/VotesDetails/AccordionTitle.tsx (2 hunks)
  • web/src/pages/Home/TopJurors/JurorCard/JurorLevel.tsx (1 hunks)
  • web/src/pages/Jurors/index.tsx (1 hunks)
  • web/src/pages/Profile/Cases/index.tsx (1 hunks)
  • web/src/pages/Profile/JurorCard/BottomContent/Coherence.tsx (2 hunks)
  • web/src/pages/Profile/JurorCard/BottomContent/JurorRewards.tsx (3 hunks)
  • web/src/pages/Profile/JurorCard/BottomContent/PixelArt.tsx (3 hunks)
  • web/src/pages/Profile/JurorCard/BottomContent/index.tsx (1 hunks)
  • web/src/pages/Profile/JurorCard/Header.tsx (2 hunks)
  • web/src/pages/Profile/JurorCard/StakingRewards.tsx (2 hunks)
  • web/src/pages/Profile/JurorCard/TopContent/index.tsx (1 hunks)
  • web/src/pages/Profile/JurorCard/index.tsx (3 hunks)
  • web/src/pages/Profile/Stakes/index.tsx (5 hunks)
  • web/src/pages/Profile/Votes/index.tsx (1 hunks)
  • web/src/pages/Profile/index.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • web/src/pages/Home/TopJurors/JurorCard/JurorLevel.tsx
  • web/src/components/Popup/MiniGuides/JurorLevels.tsx
  • web/src/pages/Cases/CaseDetails/Voting/VotesDetails/AccordionTitle.tsx
  • web/src/pages/Profile/Stakes/index.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
web/src/pages/Profile/Votes/index.tsx

[error] 2-3: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)

⏰ Context from checks skipped due to timeout of 90000ms (16)
  • GitHub Check: Redirect rules - kleros-v2-testnet-devtools
  • GitHub Check: Header rules - kleros-v2-testnet-devtools
  • GitHub Check: Pages changed - kleros-v2-testnet-devtools
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet
  • GitHub Check: Redirect rules - kleros-v2-university
  • GitHub Check: Header rules - kleros-v2-university
  • GitHub Check: Pages changed - kleros-v2-university
  • GitHub Check: contracts-testing
  • GitHub Check: SonarCloud
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Mend Security Check
🔇 Additional comments (27)
web/src/pages/Profile/JurorCard/TopContent/index.tsx (2)

18-21: LGTM! Well-defined interface.

The interface uses appropriate types, including a template literal type for the address to ensure it's properly formatted.


23-34: LGTM! Clean implementation with good UX considerations.

The component is well-structured with:

  • Proper type annotations
  • Good conditional rendering
  • Appropriate pluralization logic for case counts
web/src/layout/Header/navbar/Menu/Settings/General/WalletAndProfile.tsx (1)

55-57: Verify the new profile route.

The simplified profile path aligns with the PR's restructuring objectives. However, let's verify that the new route is properly handled.

web/src/pages/Profile/JurorCard/BottomContent/Coherence.tsx (1)

8-8: Verify ILevelCriteria interface compatibility.

The change to use ILevelCriteria looks good, but let's verify the interface compatibility to ensure no breaking changes.

Also applies to: 31-31

✅ Verification successful

Interface compatibility verified.

  • The interface definition in web/src/utils/userLevelCalculation.ts now correctly includes the fields level, title, minDisputes, minCoherencePercentage, and maxCoherencePercentage.
  • Confirmed that the Coherence component (web/src/pages/Profile/JurorCard/BottomContent/Coherence.tsx) imports and uses the updated ILevelCriteria without issues.
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify ILevelCriteria interface compatibility

# Check the interface definition
ast-grep --pattern 'interface ILevelCriteria {
  $$$
  level: number;
  title: string;
  $$$
}'

# Find all usages of ILevelCriteria
rg "ILevelCriteria" --type ts --type tsx

Length of output: 607

web/src/pages/Profile/JurorCard/BottomContent/PixelArt.tsx (2)

12-15: LGTM! Clean styling implementation.

The Container styled component properly centers its content using flexbox.


Line range hint 50-60: LGTM! Good use of Container for layout.

The Container component is properly used to center the image and skeleton loader.

web/src/pages/Profile/JurorCard/BottomContent/index.tsx (2)

45-50: LGTM!

The interface is well-defined with appropriate types for all required props.


52-68: Verify if StakingRewards component requires props.

The component implementation looks good. However, StakingRewards is rendered without any props. Please verify if it needs any props from the parent component.

✅ Verification successful

I'll generate a new script to search more broadly for the definition of the StakingRewards component and inspect if it accepts any props.


StakingRewards Component Does Not Require Props

The StakingRewards component is defined using React.FC without a props interface, indicating it does not accept or require any props. The code is implemented correctly.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if StakingRewards component accepts any props in its implementation.

# Search for StakingRewards component definition
ast-grep --pattern 'const StakingRewards: React.FC$_'

Length of output: 55


Script:

#!/bin/bash
# Search for occurrences of "StakingRewards" within TypeScript/TSX files
rg --ignore-case --max-columns=200 -C 2 "StakingRewards" .

Length of output: 1525

web/src/pages/Profile/JurorCard/index.tsx (3)

29-33: LGTM!

The renaming of the interface and component from JurorInfo to JurorCard aligns with the PR objectives and improves clarity.


23-26: LGTM!

The layout adjustments improve spacing consistency by:

  • Reducing the gap between elements
  • Adding horizontal padding to the card

49-51: LGTM!

The component composition with TopContent and BottomContent improves modularity and separation of concerns. Props are correctly passed to child components.

web/src/pages/Jurors/index.tsx (1)

58-61: LGTM!

The simplified navigation path to "/profile" aligns with the PR objectives and improves URL structure.

web/src/pages/Profile/JurorCard/BottomContent/JurorRewards.tsx (3)

18-28: LGTM! Responsive layout improvements.

The Container's styling enhancements with centered alignment and responsive gaps improve the layout consistency across different screen sizes.


30-34: LGTM! Well-structured token rewards container.

The new TokenRewardsContainer provides a clean and consistent layout for displaying token rewards with appropriate spacing.


59-63: LGTM! Improved token rewards rendering.

The TokenRewardsContainer wrapping provides better structure and consistency for the rewards display.

web/src/pages/Profile/Cases/index.tsx (2)

47-50: LGTM! Efficient pagination calculation.

Good use of useMemo for optimizing the total pages calculation, with proper dependencies.


53-64: LGTM! Clean component implementation.

Well-structured component with proper type safety and clean navigation handling.

web/src/pages/Profile/JurorCard/Header.tsx (2)

76-76: LGTM! Simplified header title.

Clean and focused header implementation.


Line range hint 82-86: LGTM! Well-implemented social sharing.

Good conditional rendering of the social sharing feature based on user activity.

web/src/app.tsx (1)

Line range hint 76-82: LGTM! More flexible routing structure.

The wildcard route path provides better flexibility for handling sub-routes in the profile section, supporting the new tabbed navigation structure.

web/src/components/EvidenceCard.tsx (1)

226-226: LGTM! URL structure simplified.

The profile link has been updated to use the new standardized URL structure, aligning with the profile page restructuring.

web/src/components/JurorLink.tsx (3)

43-45: LGTM! Interface design is clear and flexible.

The renamed interface with the optional isInternalLink prop provides good flexibility for different use cases.


48-56: LGTM! Well-implemented link handling logic.

The link handling logic is well-structured:

  • Proper handling of connected user's profile
  • Clear distinction between internal and external links
  • Efficient use of useMemo for explorer link

61-67: LGTM! Secure external link implementation.

Good security practices implemented:

  • Proper rel attributes for external links
  • Appropriate target attribute handling
  • Clear visual distinction with different icons
web/src/pages/Profile/index.tsx (2)

56-60: LGTM! Well-structured tab configuration.

The TABS array provides a clean, maintainable way to define the navigation structure.


74-79: LGTM! Clean tab navigation implementation.

The handleTabChange function properly handles:

  • URL path updates
  • Query parameter preservation
  • Navigation state management
web/src/pages/Profile/Votes/index.tsx (1)

5-7: Implementation appears incomplete.

The empty div suggests this component is a work in progress. Consider adding a TODO comment or implementing a basic structure for the votes display.

Would you like me to help implement a basic structure for the Votes component?

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

🧹 Nitpick comments (6)
web/src/pages/Profile/Cases/index.tsx (3)

33-36: Pagination and Filter Decoding Logic
The calculation of pageNumber using parseInt(page ?? "1") and subsequent derivations for pagination (e.g., disputeSkip) and filter decoding with decodeURIFilter(filter ?? "all") are straightforward.
Note: Consider guarding against non-numeric page values to avoid potential NaN results. For example, you might add a fallback in case parseInt returns NaN.


44-46: User Data Query and Total Cases Calculation
The use of useUserQuery to retrieve user data is correct. However, when calculating totalResolvedCases, using parseInt(userData?.user?.totalResolvedDisputes) could yield NaN if the value is undefined. Consider providing a fallback default value. For example:

-  const totalResolvedCases = parseInt(userData?.user?.totalResolvedDisputes);
+  const totalResolvedCases = parseInt(userData?.user?.totalResolvedDisputes || "0", 10);

This ensures that the parsed value is always a number.


52-65: Rendering and Navigation Logic in CasesDisplay
The StyledCasesDisplay component is rendered with the correct props for title, disputes, pagination details, and callbacks. A couple of suggestions:

  • The disputes prop handles the case when userData?.user is not null, but you might consider a more robust existence check (e.g., checking truthiness) if there’s any possibility for an undefined value.
  • In the setCurrentPage callback, constructing the navigation URL directly with potentially undefined order or filter may lead to unexpected URL segments. Consider providing default fallbacks for these parameters. For instance:
-      setCurrentPage={(newPage: number) =>
-        navigate(`${location}/${newPage}/${order}/${filter}?${searchParams.toString()}`)
-      }
+      setCurrentPage={(newPage: number) =>
+        navigate(`${location}/${newPage}/${order ?? "desc"}/${filter ?? "all"}?${searchParams.toString()}`)
+      }

This ensures a more predictable URL structure.

web/src/pages/Profile/JurorCard/BottomContent/index.tsx (1)

32-43: LeftContent Styled Component – Redundant Property
There is a redundancy in the flex-direction definitions. Lines 34 and 36 both set flex-direction (first as row, then as column), with the latter overwriting the former. Verify the intended default layout and remove the redundant declaration for clarity.

Proposed Diff:

 const LeftContent = styled.div`
-  display: flex;
-  flex-direction: row;
-  gap: 48px;
-  flex-direction: column;
+  display: flex;
+  gap: 48px;
+  flex-direction: column;
web/src/pages/Profile/Stakes/index.tsx (1)

Line range hint 41-54: Data Filtering and Reuse
The component calculates stakedCourts by filtering tokens with a staked value greater than 0 and then uses this to determine if any stakes exist. However, the same filtering logic is reapplied later during the mapping. Consider reusing the already computed stakedCourts variable for rendering to avoid duplication and improve clarity.

Proposed Diff:

-      {isStaked && !isLoading ? (
-        <CourtCardsContainer>
-          {stakeData?.jurorTokensPerCourts
-            ?.filter(({ staked }) => staked > 0)
-            .map(({ court: { id, name }, staked }) => (
-              <CourtCard key={id} name={name ?? ""} stake={staked} {...{ id }} />
-            ))}
-        </CourtCardsContainer>
-      ) : null}
+      {isStaked && !isLoading ? (
+        <CourtCardsContainer>
+          {stakedCourts?.map(({ court: { id, name }, staked }) => (
+            <CourtCard key={id} name={name ?? ""} stake={staked} {...{ id }} />
+          ))}
+        </CourtCardsContainer>
+      ) : null}
web/src/pages/Profile/index.tsx (1)

62-65: getTabIndex Function Logic
The getTabIndex function uses a heuristic based on the inclusion of the tab path (splitting on /) to determine the active tab. While this approach works, it may be sensitive to URL structure changes. Consider a more robust strategy if the URL patterns become more complex.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c3213ca and f701d8f.

📒 Files selected for processing (7)
  • web/src/pages/Profile/Cases/index.tsx (1 hunks)
  • web/src/pages/Profile/JurorCard/BottomContent/JurorRewards.tsx (4 hunks)
  • web/src/pages/Profile/JurorCard/BottomContent/index.tsx (1 hunks)
  • web/src/pages/Profile/JurorCard/Header.tsx (2 hunks)
  • web/src/pages/Profile/JurorCard/index.tsx (3 hunks)
  • web/src/pages/Profile/Stakes/index.tsx (5 hunks)
  • web/src/pages/Profile/index.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/src/pages/Profile/JurorCard/BottomContent/JurorRewards.tsx
⏰ Context from checks skipped due to timeout of 90000ms (15)
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Redirect rules - kleros-v2-testnet-devtools
  • GitHub Check: Redirect rules - kleros-v2-university
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet-devtools
  • GitHub Check: Header rules - kleros-v2-university
  • GitHub Check: Pages changed - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet-devtools
  • GitHub Check: Pages changed - kleros-v2-university
  • GitHub Check: contracts-testing
  • GitHub Check: Analyze (javascript)
  • GitHub Check: SonarCloud
🔇 Additional comments (28)
web/src/pages/Profile/Cases/index.tsx (7)

1-2: Import Statements are Clear and Concise
The required dependencies such as React, hooks from react-router-dom, and other utilities are imported correctly.


15-21: Well-Defined Styled Component
The StyledCasesDisplay component is defined using styled-components with responsive styling which is clear and maintainable.


23-25: Explicit Interface Declaration
The ICases interface uses a template literal type for searchParamAddress (e.g. "0x${string}"), which enforces the expected address format. Ensure that this type is consistently used across the codebase.


27-32: Component Setup Using React Hooks
The Cases component properly leverages hooks like useParams, useSearchParams, useRootPath, and useNavigate for routing and URL parameter management. The use of searchParamAddress as a prop for data queries is appropriate.


37-42: GraphQL Query for Cases is Appropriately Configured
The useMyCasesQuery hook is called with well-calculated parameters including searchParamAddress, disputeSkip, decodedFilter, and an order determined by "asc" === order. This logic looks solid assuming descending is the intended default when order isn’t "asc".


47-50: Efficient Calculation of Total Pages with useMemo
Utilizing useMemo to calculate totalPages based on totalCases and casesPerPage is a good optimization. The conditional check using isUndefined(totalCases) ensures a fallback value.


68-68: Export Statement is Standard
The component is exported as the default export, which is consistent with common practices for React components.

web/src/pages/Profile/JurorCard/BottomContent/index.tsx (4)

1-12: Import and Dependency Declarations
The import statements are clear and appropriately ordered. All necessary dependencies such as React, styled-components, and the local components are imported correctly.


13-30: Container Styled Component – Responsive Layout
The Container component uses landscapeStyle effectively to adjust the flex-direction based on screen orientation. The layout properties (gap, width, height) are clearly organized.


45-50: Interface Declaration for IBottomContent
The IBottomContent interface is well-defined. Using a literal template type for searchParamAddress (i.e., 0x${string}) is a neat way to enforce address formatting.


52-68: BottomContent Component Implementation
The functional component cleanly destructures its props and composes the UI by arranging child components inside Container and LeftContent. The use of the spread operator to pass multiple props is concise.

web/src/pages/Profile/JurorCard/index.tsx (4)

Line range hint 1-11: Component and Utility Imports Cleanup
The file imports required modules and utilities. The removal of unused imports (e.g., landscapeStyle and responsiveSize previously present) helps keep the file clean.


23-26: Card Styled Component Adjustments
The modifications to the Card styling (e.g., gap changed to 24px and padding adjusted) are appropriate for the new layout structure.


29-30: Interface Rename for JurorCard
Renaming the interface property from addressToQuery to searchParamAddress is consistent with the overall refactor.


Line range hint 33-57: JurorCard Component Logic and Data Handling
The component correctly retrieves user data via useUserQuery, computes totals using parseInt, and derives user level information with external utilities. Spreading props into the <Header> and <BottomContent> components promotes code reuse. Ensure that the external functions (getCoherencePercent and getUserLevelData) remain pure and handle edge cases.

web/src/pages/Profile/Stakes/index.tsx (2)

Line range hint 1-16: Imports and Styled Components in Stakes
The file imports necessary hooks and styling utilities. The styled components (Container, CourtCardsContainer, and StyledLabel) are defined clearly and use responsive utilities appropriately.


37-41: Interface and Component Renaming Consistency
Renaming the interface from ICourts to IStakes and the component from Courts to Stakes is well executed. The use of a literal type for searchParamAddress continues the standardized naming.

web/src/pages/Profile/JurorCard/Header.tsx (4)

Line range hint 1-13: Header Component Imports and Styling
All import statements and styled components (e.g., Container, StyledTitle, LinksContainer) are well set up. The styles are consistent with the design system.


54-62: Interface Update for Header Component
The update of the interface property from addressToQuery to searchParamAddress is consistent with the overall refactor. This promotes uniformity across components handling juror addresses.


62-72: Header Component Logic – Conditional Rendering
The component computes a share URL based on the juror’s data. Note that the share link is conditionally rendered only when totalResolvedVotes > 0 and !searchParamAddress is true. Double-check that this logic aligns with the intended UX—if a searchParamAddress is always provided in profile context, the share link may be unintentionally hidden.


Line range hint 72-79: Social Sharing and Link Handling
The construction of xPostText and xShareUrl using window.location.origin is a practical approach. The remaining JSX is clear and succinct, focusing on displaying a static title ("Juror Profile") and the related interactive elements.

web/src/pages/Profile/index.tsx (7)

1-9: Main Profile Page – Imports and Initial Setup
The file sets up necessary React hooks and external component dependencies (such as routing and UI components). The inclusion and use of TabsComponent and icon imports (PnkIcon, DocIcon, VotedIcon) are well organized.


35-44: StyledTabs Component Definition
The StyledTabs component is defined with responsive margins and font sizes. The selector for child SVGs to adjust spacing is a nice touch.


56-60: TABS Constant Definition
The TABS array clearly defines the tab structure with text labels, numerical values, icons, and paths. This explicit declaration makes it easy to maintain and update the navigation tabs.


73-77: useEffect for Navigation on Missing Address
The useEffect hook checks if the wallet is connected and no search parameter is provided, then automatically updates the URL with the connected address. This improves user experience by ensuring the profile always has an address parameter.


79-84: handleTabChange Functionality
The handleTabChange function correctly constructs new paths based on the selected tab and preserves the search parameter. The logic is straightforward and integrates well with useNavigate.


88-106: Conditional Rendering with Routes
The conditional rendering shows the JurorCard, StyledTabs, and a set of Route definitions when a searchParamAddress is present. The fallback <Navigate> route ensures that any unexpected paths default to the stakes tab. This structure is both flexible and maintainable.


106-114: Fallback for Unconnected Users
The component gracefully handles the case when the user is not connected by rendering a call to action within the ConnectWalletContainer.

@sonarqubecloud
Copy link

sonarqubecloud bot commented Feb 7, 2025

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: 2

🧹 Nitpick comments (4)
web/src/pages/Profile/Stakes/index.tsx (1)

46-62: Consider these improvements to the component logic.

  1. Extract the filter logic to avoid duplication:
+ const filterStakedCourts = (courts) => 
+   courts?.filter(({ staked }) => staked > 0) ?? [];
- const stakedCourts = stakeData?.jurorTokensPerCourts?.filter(({ staked }) => staked > 0);
+ const stakedCourts = filterStakedCourts(stakeData?.jurorTokensPerCourts);
  ...
- {stakeData?.jurorTokensPerCourts
-   ?.filter(({ staked }) => staked > 0)
+ {filterStakedCourts(stakeData?.jurorTokensPerCourts)
    .map(({ court: { id, name }, staked }) => (
  1. Consider making the empty state message more informative:
- <StyledLabel>No stakes found</StyledLabel>
+ <StyledLabel>No active stakes found for this address</StyledLabel>
  1. Consider showing a more accurate loading skeleton:
- {isLoading ? <Skeleton /> : null}
+ {isLoading ? (
+   <CourtCardsContainer>
+     <Skeleton height={80} count={3} />
+   </CourtCardsContainer>
+ ) : null}
web/src/pages/Profile/Stakes/Header.tsx (3)

66-69: Consider adding prop validation for stake values.

The interface accepts strings for stake values but doesn't enforce any validation. Consider adding validation to ensure the strings are valid numeric values.

 interface IHeader {
-  totalStake: string;
-  lockedStake: string;
+  totalStake: `${number}` | `0x${string}`;
+  lockedStake: `${number}` | `0x${string}`;
 }

79-87: Consider using early returns for better readability.

The nested ternary operators with null returns can be simplified using early returns.

-        {!isUndefined(totalStake) ? (
-          <StakedPnk>
-            <StyledPnkIcon />
-            <label> Total Stake: </label>
-            <small>
-              <NumberDisplay value={formattedTotalStake} unit="PNK" />
-            </small>
-          </StakedPnk>
-        ) : null}
-        {!isUndefined(lockedStake) ? (
-          <LockedPnk>
-            <StyledLockerIcon />
-            <label> Locked Stake: </label>
-            <small>
-              <NumberDisplay value={formattedLockedStake} unit="PNK" />
-            </small>
-          </LockedPnk>
-        ) : null}
+        {!isUndefined(totalStake) && (
+          <StakedPnk>
+            <StyledPnkIcon />
+            <label> Total Stake: </label>
+            <small>
+              <NumberDisplay value={formattedTotalStake} unit="PNK" />
+            </small>
+          </StakedPnk>
+        )}
+        {!isUndefined(lockedStake) && (
+          <LockedPnk>
+            <StyledLockerIcon />
+            <label> Locked Stake: </label>
+            <small>
+              <NumberDisplay value={formattedLockedStake} unit="PNK" />
+            </small>
+          </LockedPnk>
+        )}

Also applies to: 88-96


56-64: Consider extracting icon styles to a shared component.

Both StyledPnkIcon and StyledLockerIcon share similar styles. Consider creating a shared styled component.

+const BaseIcon = css`
+  fill: ${({ theme }) => theme.secondaryPurple};
+  width: 14px;
+`;
+
-const StyledPnkIcon = styled(PnkIcon)`
-  fill: ${({ theme }) => theme.secondaryPurple};
-  width: 14px;
-`;
+const StyledPnkIcon = styled(PnkIcon)`${BaseIcon}`;

-const StyledLockerIcon = styled(LockerIcon)`
-  fill: ${({ theme }) => theme.secondaryPurple};
-  width: 14px;
-`;
+const StyledLockerIcon = styled(LockerIcon)`${BaseIcon}`;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f701d8f and c0c8158.

📒 Files selected for processing (3)
  • web/src/components/NumberDisplay.tsx (2 hunks)
  • web/src/pages/Profile/Stakes/Header.tsx (1 hunks)
  • web/src/pages/Profile/Stakes/index.tsx (4 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (8)
  • GitHub Check: Redirect rules - kleros-v2-university
  • GitHub Check: Header rules - kleros-v2-university
  • GitHub Check: Pages changed - kleros-v2-university
  • GitHub Check: Redirect rules - kleros-v2-neo
  • GitHub Check: Header rules - kleros-v2-neo
  • GitHub Check: Pages changed - kleros-v2-neo
  • GitHub Check: contracts-testing
  • GitHub Check: Mend Security Check
🔇 Additional comments (4)
web/src/pages/Profile/Stakes/index.tsx (2)

14-29: LGTM! Styling adjustments improve visual consistency.

The reduced margins and gaps create a more compact and consistent layout across different screen sizes.


35-45: LGTM! Type-safe interface and component changes.

The renamed interface and component better reflect their purpose, and the use of template literal type for Ethereum addresses improves type safety.

web/src/components/NumberDisplay.tsx (1)

4-5: LGTM! Good addition of the commify utility.

The addition of the commify function will improve readability of large numbers by adding thousand separators.

web/src/pages/Profile/Stakes/Header.tsx (1)

16-30: LGTM! Good use of responsive design.

The container's layout adapts well to different screen sizes using responsiveSize and landscapeStyle.

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

🧹 Nitpick comments (1)
web/src/pages/Profile/Stakes/CourtCard/CourtName.tsx (1)

41-44: Consider these improvements for better type safety and accessibility.

  1. The interface doesn't match the implementation:
    • id is treated as optional in the JSX (line 50: id?.toString())
    • But it's required in the interface
  2. Add aria-label to improve accessibility of the arrow icon
 interface ICourtName {
   name: string;
-  id: string;
+  id?: string;
 }

 <ReStyledArrowLink to={`/courts/${id?.toString()}`}>
-  Open Court <ArrowIcon />
+  Open Court <ArrowIcon aria-label="Navigate to court details" />
 </ReStyledArrowLink>

Also applies to: 50-52

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c0c8158 and 59b50e9.

📒 Files selected for processing (3)
  • web/src/components/NumberDisplay.tsx (3 hunks)
  • web/src/pages/Profile/Stakes/CourtCard/CourtName.tsx (1 hunks)
  • web/src/pages/Profile/Stakes/Header.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • web/src/components/NumberDisplay.tsx
  • web/src/pages/Profile/Stakes/Header.tsx
⏰ Context from checks skipped due to timeout of 90000ms (15)
  • GitHub Check: Redirect rules - kleros-v2-testnet-devtools
  • GitHub Check: Header rules - kleros-v2-testnet-devtools
  • GitHub Check: Pages changed - kleros-v2-testnet-devtools
  • GitHub Check: Redirect rules - kleros-v2-neo
  • GitHub Check: Header rules - kleros-v2-neo
  • GitHub Check: Pages changed - kleros-v2-neo
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet
  • GitHub Check: Analyze (javascript)
  • GitHub Check: contracts-testing
  • GitHub Check: SonarCloud
🔇 Additional comments (1)
web/src/pages/Profile/Stakes/CourtCard/CourtName.tsx (1)

14-14: LGTM! Improved responsive layout.

The styling changes enhance the component's flexibility and mobile responsiveness:

  • Split gap values provide better control over spacing
  • flex-wrap ensures proper content flow on smaller screens

Also applies to: 17-17

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: 10

🧹 Nitpick comments (14)
web/src/pages/Profile/index.tsx (2)

62-64: Enhance path matching robustness.

The current path matching logic using includes() might lead to false positives with partial matches. For example, "stakes" would match both "/profile/stakes" and "/profile/mistakes".

Consider using a more precise path matching approach:

-  return TABS.findIndex((tab) => currentPath.includes(tab.path.split("/")[0]));
+  return TABS.findIndex((tab) => {
+    const tabBase = tab.path.split("/")[0];
+    const currentBase = currentPath.split("/").find(segment => 
+      ["stakes", "cases", "votes"].includes(segment)
+    );
+    return tabBase === currentBase;
+  });

79-84: Add error handling for invalid tab indices.

The handleTabChange function assumes the tab index will always be valid. While this is likely true for UI-triggered changes, it's good practice to add validation.

Consider adding validation:

 const handleTabChange = (tabIndex: number) => {
+  if (tabIndex < 0 || tabIndex >= TABS.length) {
+    console.error(`Invalid tab index: ${tabIndex}`);
+    return;
+  }
   const selectedTab = TABS[tabIndex];
   const basePath = `/profile/${selectedTab.path}`;
   const queryParam = searchParamAddress ? `?address=${searchParamAddress}` : "";
   navigate(`${basePath}${queryParam}`);
 };
web/src/pages/Profile/Votes/index.tsx (1)

41-41: Replace hardcoded pagination value.

The total number of pages is hardcoded with a TODO comment.

Would you like me to help implement the logic to calculate the total pages based on the total number of votes and votes per page?

web/src/pages/Profile/Stakes/CourtCard/Stake.tsx (2)

17-19: Improve type safety for stake prop.

Consider using a more specific type for the stake prop to ensure it's a valid hex string.

 interface IStake {
-  stake: string;
+  stake: `0x${string}`;
 }

21-29: Add error handling and improve accessibility.

  1. Add error handling for invalid stake values
  2. Add aria-label for better screen reader support
  3. Consider adding a title attribute for tooltip on hover
 const Stake: React.FC<IStake> = ({ stake }) => {
+  if (!stake.startsWith('0x')) {
+    console.error('Invalid stake format');
+    return null;
+  }
+
   const formattedStake = formatUnits(stake, 18);
 
   return (
-    <StyledLabel>
+    <StyledLabel
+      aria-label={`Stake amount: ${formattedStake} PNK`}
+      title={`${formattedStake} PNK`}
+    >
       <NumberDisplay value={formattedStake} unit="PNK" />
     </StyledLabel>
   );
 };
web/src/pages/Profile/Stakes/CourtCard/CourtName.tsx (1)

33-39: Improve accessibility and use semantic HTML.

  1. Use semantic HTML elements for better structure
  2. Add ARIA attributes for better screen reader support
  3. Add title attribute for tooltip on hover
 const CourtName: React.FC<ICourtName> = ({ name, id }) => {
   return (
-    <Container>
-      <small>{name}</small>
+    <Container
+      role="heading"
+      aria-level={3}
+      title={`Court: ${name}`}
+    >
+      <small aria-label={`Court name: ${name}`}>{name}</small>
     </Container>
   );
 };
web/src/pages/Profile/Stakes/index.tsx (1)

40-40: Extract magic numbers into constants.

The hardcoded values in useStakingHistory(1, 0) should be extracted into named constants or configuration values for better maintainability.

+const DEFAULT_PAGE = 1;
+const DEFAULT_OFFSET = 0;
+
 const Stakes: React.FC<IStakes> = ({ searchParamAddress }) => {
   // ...
-  const { data: stakingHistoryData } = useStakingHistory(1, 0);
+  const { data: stakingHistoryData } = useStakingHistory(DEFAULT_PAGE, DEFAULT_OFFSET);
   // ...
 };
web/src/pages/Profile/Stakes/CurrentStakes/index.tsx (1)

46-48: Remove duplicate filter logic.

The filtering of staked courts is duplicated. Consider storing the filtered result in a variable to avoid redundant computation.

 const stakedCourts = currentStakeData?.jurorTokensPerCourts?.filter(({ staked }) => staked > 0);
 const isStaked = stakedCourts && stakedCourts.length > 0;

 return (
   <Container>
     <Header {...{ totalStake, lockedStake }} />
     {!isStaked && !isLoading ? (
       <NoCurrentStakesLabel>No stakes found</NoCurrentStakesLabel>
     ) : isLoading ? (
       <Skeleton />
     ) : null}
     {isStaked && !isLoading ? (
       <CourtCardsContainer>
-        {currentStakeData?.jurorTokensPerCourts
-          ?.filter(({ staked }) => staked > 0)
+        {stakedCourts
           .map(({ court: { id, name }, staked }) => (
             <CourtCard key={id} name={name ?? ""} stake={staked} {...{ id }} />
           ))}
       </CourtCardsContainer>
     ) : null}
   </Container>
 );
web/src/hooks/queries/useStakingHistory.ts (1)

28-46: Move GraphQL query to a separate file.

Large GraphQL queries should be maintained in separate files for better maintainability and reusability.

Create a new file queries/stakingHistory.ts:

export const GET_STAKING_EVENTS = `
  query GetStakingEvents($pagination: PaginationArgs) {
    userStakingEvents(pagination: $pagination) {
      edges {
        node {
          name
          args
          blockTimestamp
          transactionHash
        }
        cursor
      }
      count
      hasNextPage
    }
  }
`;
web/src/pages/Profile/Stakes/CourtCard/index.tsx (3)

18-42: Consider extracting theme-specific styles to theme configuration.

The Container component mixes theme-specific styles within the component. Consider extracting the box-shadow style to the theme configuration for better maintainability.

-  ${({ theme }) => (theme.name === "light" ? `box-shadow: 0px 2px 3px 0px ${theme.stroke};` : "")}

Add to theme configuration:

// theme.ts
export const lightTheme = {
  // ... other theme properties
  cardBoxShadow: '0px 2px 3px 0px ${theme.stroke}',
};

export const darkTheme = {
  // ... other theme properties
  cardBoxShadow: 'none',
};

Then update the styled component:

+  box-shadow: ${({ theme }) => theme.cardBoxShadow};

73-80: Consider adding JSDoc comments and prop validation.

The interface would benefit from JSDoc comments explaining the purpose of each prop and any constraints. Also, consider adding runtime prop validation.

+/**
+ * Interface for CourtCard component props
+ * @property {string} name - The name of the court
+ * @property {string} stake - The stake amount
+ * @property {string} id - The court ID
+ * @property {number} [timestamp] - Optional timestamp of the staking event
+ * @property {string} [transactionHash] - Optional transaction hash
+ * @property {boolean} [isCurrentStakeCard] - Whether this is the current stake card
+ */
 interface ICourtCard {
   name: string;
   stake: string;
   id: string;
   timestamp?: number;
   transactionHash?: string;
   isCurrentStakeCard?: boolean;
 }

Also applies to: 82-89


98-101: Add aria-label for better accessibility.

The external link should have an aria-label to improve accessibility.

-              <ReStyledArrowLink to={getTxnExplorerLink(transactionHash)} target="_blank" rel="noopener noreferrer">
+              <ReStyledArrowLink 
+                to={getTxnExplorerLink(transactionHash)} 
+                target="_blank" 
+                rel="noopener noreferrer"
+                aria-label={`View transaction ${transactionHash} in explorer`}
+              >
web/src/pages/Profile/Stakes/StakingHistory.tsx (2)

55-57: Consider using a more semantic loading state.

The skeleton loading state could be more semantic and accessible.

-          Array.from({ length: 10 }).map((_, index) => <Skeleton height={64} key={index} />)
+          <div role="status" aria-busy="true" aria-label="Loading staking history">
+            {Array.from({ length: 10 }).map((_, index) => (
+              <Skeleton height={64} key={index} />
+            ))}
+          </div>

54-73: Consider virtualizing the list for better performance.

For large lists of staking events, consider using a virtualized list component to improve performance.

+import { FixedSizeList } from 'react-window';

-      <CourtCardsContainer>
+      <FixedSizeList
+        height={600}
+        width="100%"
+        itemCount={stakingEvents.length}
+        itemSize={64}
+      >
+        {({ index, style }) => {
+          const { node, cursor } = stakingEvents[index];
+          const courtName = findCourtNameById(courtTreeData, node.args._courtID);
+          return (
+            <div style={style}>
               <CourtCard
-                key={cursor}
+                key={`${cursor}-${index}`}
                 name={courtName}
                 stake={node.args._amount}
                 id={node.args._courtID}
                 isCurrentStakeCard={false}
                 timestamp={node.blockTimestamp}
                 transactionHash={node.transactionHash}
               />
+            </div>
+          );
+        }}
+      </FixedSizeList>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 59b50e9 and 6908af8.

📒 Files selected for processing (19)
  • web/src/components/EvidenceCard.tsx (1 hunks)
  • web/src/components/JurorLink.tsx (2 hunks)
  • web/src/hooks/queries/useStakingHistory.ts (1 hunks)
  • web/src/layout/Header/navbar/Menu/Settings/General/WalletAndProfile.tsx (1 hunks)
  • web/src/pages/Cases/CaseDetails/Voting/VotesDetails/AccordionTitle.tsx (2 hunks)
  • web/src/pages/Jurors/index.tsx (1 hunks)
  • web/src/pages/Profile/Stakes/CourtCard/CourtName.tsx (1 hunks)
  • web/src/pages/Profile/Stakes/CourtCard/Stake.tsx (1 hunks)
  • web/src/pages/Profile/Stakes/CourtCard/index.tsx (1 hunks)
  • web/src/pages/Profile/Stakes/CurrentStakes/Header.tsx (1 hunks)
  • web/src/pages/Profile/Stakes/CurrentStakes/index.tsx (1 hunks)
  • web/src/pages/Profile/Stakes/StakingHistory.tsx (1 hunks)
  • web/src/pages/Profile/Stakes/index.tsx (1 hunks)
  • web/src/pages/Profile/Votes/StatsAndFilters/Filters.tsx (1 hunks)
  • web/src/pages/Profile/Votes/StatsAndFilters/Stats.tsx (1 hunks)
  • web/src/pages/Profile/Votes/StatsAndFilters/index.tsx (1 hunks)
  • web/src/pages/Profile/Votes/index.tsx (1 hunks)
  • web/src/pages/Profile/index.tsx (3 hunks)
  • web/src/utils/findCourtNameById.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • web/src/layout/Header/navbar/Menu/Settings/General/WalletAndProfile.tsx
  • web/src/components/EvidenceCard.tsx
  • web/src/pages/Cases/CaseDetails/Voting/VotesDetails/AccordionTitle.tsx
  • web/src/pages/Jurors/index.tsx
⏰ Context from checks skipped due to timeout of 90000ms (13)
  • GitHub Check: Redirect rules - kleros-v2-testnet-devtools
  • GitHub Check: Header rules - kleros-v2-testnet-devtools
  • GitHub Check: Redirect rules - kleros-v2-neo
  • GitHub Check: Redirect rules - kleros-v2-university
  • GitHub Check: Pages changed - kleros-v2-testnet-devtools
  • GitHub Check: Header rules - kleros-v2-neo
  • GitHub Check: Header rules - kleros-v2-university
  • GitHub Check: Pages changed - kleros-v2-neo
  • GitHub Check: Pages changed - kleros-v2-university
  • GitHub Check: SonarCloud
  • GitHub Check: Analyze (javascript)
  • GitHub Check: contracts-testing
  • GitHub Check: Mend Security Check
🔇 Additional comments (7)
web/src/pages/Profile/index.tsx (2)

35-46: Well-structured responsive styling implementation!

The StyledTabs component effectively uses responsive sizing and proper theme variables, ensuring consistent styling across different screen sizes.


96-109: Clean and well-structured routing implementation!

The routing setup effectively handles all tab paths and includes a proper fallback route. The consistent use of prop spreading maintains clean code while passing necessary data to child components.

web/src/pages/Profile/Votes/StatsAndFilters/index.tsx (1)

1-24: LGTM! Well-structured component with clear separation of concerns.

The component follows React best practices with proper TypeScript typing and styled-components usage.

web/src/pages/Profile/Stakes/CurrentStakes/index.tsx (1)

32-35: LGTM! Clean component structure with proper state handling.

The component effectively manages different states (loading, empty, and data) and follows React best practices.

web/src/components/JurorLink.tsx (1)

48-71: LGTM! Well-structured component with proper security considerations.

The component:

  • Correctly handles both internal and external links
  • Implements proper security attributes for external links
  • Uses memoization appropriately
  • Follows React best practices
web/src/pages/Profile/Stakes/CurrentStakes/Header.tsx (1)

75-99: LGTM! Clean and responsive layout implementation.

The component implements a clean and responsive layout using styled-components and proper conditional rendering.

web/src/pages/Profile/Stakes/CourtCard/index.tsx (1)

90-111: LGTM! Well-structured component with good semantic markup.

The component implementation is clean, follows React best practices, and uses appropriate semantic HTML elements. The conditional rendering is handled well.

@qlty-cloud-legacy
Copy link

Code Climate has analyzed commit 4172322 and detected 71 issues on this pull request.

Here's the issue category breakdown:

Category Count
Complexity 6
Duplication 13
Style 52

View more on Code Climate.

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

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: 8

🧹 Nitpick comments (9)
web/src/pages/Profile/Votes/VoteCard/Round.tsx (1)

15-17: Consider using number type for the number prop.

The number prop represents a round number but is typed as string. Consider using the number type for better type safety and to avoid unnecessary type conversions.

 interface IRound {
-  number: string;
+  number: number;
 }
web/src/pages/Profile/Votes/VoteCard/CourtName.tsx (1)

6-26: Consider simplifying the Container styling.

The Container has complex styling with multiple flex properties. Consider simplifying:

  1. flex-direction: row is the default and can be removed
  2. The gap property could use a single value if both horizontal and vertical gaps are the same
 const Container = styled.div`
   display: flex;
   width: 100%;
-  flex-direction: row;
-  gap: 8px 16px;
+  gap: 16px;
   align-items: center;
   justify-content: space-between;
   flex-wrap: wrap;

   small {
     height: 100%;
     font-weight: 400;
   }

   ${landscapeStyle(
     () => css`
       justify-content: flex-start;
       width: auto;
     `
   )}
 `;
web/src/pages/Profile/Votes/VoteCard/Vote.tsx (1)

26-28: Consider using a union type for vote choices.

To improve type safety and prevent invalid vote choices, consider using a union type.

+type VoteChoice = 'Yes' | 'No' | 'Refuse to Vote' | 'Pending';
+
 interface IVote {
-  choice: string;
+  choice: VoteChoice;
 }
web/src/pages/Profile/Votes/VoteCard/CaseNumber.tsx (1)

8-28: Consider simplifying the Container styling.

Similar to CourtName, the Container has complex styling that could be simplified:

  1. flex-direction: row is the default
  2. The gap property could use a single value
 const Container = styled.div`
   display: flex;
   width: 100%;
-  flex-direction: row;
-  gap: 8px 16px;
+  gap: 16px;
   align-items: center;
   justify-content: space-between;
   flex-wrap: wrap;

   small {
     height: 100%;
     font-weight: 600;
   }

   ${landscapeStyle(
     () => css`
       justify-content: flex-start;
       width: auto;
     `
   )}
 `;
web/src/pages/Profile/Stakes/index.tsx (1)

40-40: Extract pagination parameters to constants or props.

The hardcoded values (1, 0) in useStakingHistory should be extracted to make the component more configurable.

+const DEFAULT_PAGE = 1;
+const DEFAULT_OFFSET = 0;
+
 const Stakes: React.FC<IStakes> = ({ searchParamAddress }) => {
   const { data: currentStakeData, isLoading: isCurrentStakeLoading } = useJurorStakeDetailsQuery(searchParamAddress);
-  const { data: stakingHistoryData } = useStakingHistory(1, 0);
+  const { data: stakingHistoryData } = useStakingHistory(DEFAULT_PAGE, DEFAULT_OFFSET);
web/src/pages/Profile/Stakes/CurrentStakes/index.tsx (2)

37-38: Extract duplicate filter logic to a variable.

The filter logic for staked courts is duplicated. Extract it to a variable to maintain DRY principles.

+  const filterStakedCourts = (courts) => 
+    courts?.filter(({ staked }) => staked > 0) ?? [];
+
-  const stakedCourts = currentStakeData?.jurorTokensPerCourts?.filter(({ staked }) => staked > 0);
+  const stakedCourts = filterStakedCourts(currentStakeData?.jurorTokensPerCourts);
   const isStaked = stakedCourts && stakedCourts.length > 0;

   // ...
   {isStaked && !isCurrentStakeLoading ? (
     <CourtCardsContainer>
-      {currentStakeData?.jurorTokensPerCourts
-        ?.filter(({ staked }) => staked > 0)
+      {filterStakedCourts(currentStakeData?.jurorTokensPerCourts)
         .map(({ court: { id, name }, staked }) => (

Also applies to: 50-52


43-47: Simplify conditional rendering logic.

The nested ternary conditions make the code harder to read. Consider using early returns or a more straightforward if-else structure.

-      {!isStaked && !isCurrentStakeLoading ? (
-        <NoCurrentStakesLabel>No stakes found</NoCurrentStakesLabel>
-      ) : isCurrentStakeLoading ? (
-        <Skeleton />
-      ) : null}
+      {isCurrentStakeLoading && <Skeleton />}
+      {!isCurrentStakeLoading && !isStaked && (
+        <NoCurrentStakesLabel>No stakes found</NoCurrentStakesLabel>
+      )}
web/src/pages/Profile/Votes/VoteCard/index.tsx (1)

29-31: Improve hover interaction accessibility.

Setting cursor to 'auto' on hover might confuse users about interactivity. Consider using 'pointer' for clickable elements or removing the hover style if the element isn't interactive.

-  :hover {
-    cursor: auto;
-  }
+  :hover {
+    cursor: pointer;
+  }
web/src/pages/Profile/Stakes/StakingHistory.tsx (1)

17-17: Remove or utilize empty styled component.

The Container styled component is empty and doesn't provide any styling. Either remove it and use a regular div, or add the necessary styles.

-const Container = styled.div``;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6908af8 and 4172322.

📒 Files selected for processing (12)
  • web/src/components/DisputeView/PeriodBanner.tsx (1 hunks)
  • web/src/pages/Profile/Stakes/CurrentStakes/index.tsx (1 hunks)
  • web/src/pages/Profile/Stakes/StakingHistory.tsx (1 hunks)
  • web/src/pages/Profile/Stakes/index.tsx (1 hunks)
  • web/src/pages/Profile/Votes/StatsAndFilters/index.tsx (1 hunks)
  • web/src/pages/Profile/Votes/VoteCard/CaseNumber.tsx (1 hunks)
  • web/src/pages/Profile/Votes/VoteCard/CaseStatus.tsx (1 hunks)
  • web/src/pages/Profile/Votes/VoteCard/CourtName.tsx (1 hunks)
  • web/src/pages/Profile/Votes/VoteCard/Round.tsx (1 hunks)
  • web/src/pages/Profile/Votes/VoteCard/Vote.tsx (1 hunks)
  • web/src/pages/Profile/Votes/VoteCard/index.tsx (1 hunks)
  • web/src/pages/Profile/Votes/index.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/src/pages/Profile/Votes/StatsAndFilters/index.tsx
🧰 Additional context used
🪛 Biome (1.9.4)
web/src/pages/Profile/Votes/index.tsx

[error] 61-61: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.

(lint/correctness/useJsxKeyInIterable)

web/src/pages/Profile/Votes/VoteCard/CaseStatus.tsx

[error] 7-8: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)


[error] 49-49: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)

web/src/pages/Profile/Votes/VoteCard/index.tsx

[error] 57-58: An empty interface is equivalent to {}.

Safe fix: Use a type alias instead.

(lint/suspicious/noEmptyInterface)


[error] 60-60: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)

⏰ Context from checks skipped due to timeout of 90000ms (16)
  • GitHub Check: Redirect rules - kleros-v2-neo
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Redirect rules - kleros-v2-university
  • GitHub Check: Header rules - kleros-v2-neo
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-neo
  • GitHub Check: Header rules - kleros-v2-university
  • GitHub Check: Pages changed - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-university
  • GitHub Check: SonarCloud
  • GitHub Check: Analyze (javascript)
  • GitHub Check: contracts-testing
  • GitHub Check: Mend Security Check
🔇 Additional comments (9)
web/src/pages/Profile/Votes/VoteCard/Round.tsx (1)

19-26: LGTM! Clean and focused component implementation.

The component is well-structured with clear separation of styling and logic. The use of styled-components and SVG icon follows best practices.

web/src/pages/Profile/Votes/VoteCard/CourtName.tsx (1)

32-38: LGTM! Well-structured component with responsive design.

The component is well-implemented with proper responsive styling using landscapeStyle.

web/src/pages/Profile/Votes/VoteCard/Vote.tsx (1)

30-38: LGTM! Clean implementation with consistent styling.

The component is well-structured with proper theme usage and icon styling.

web/src/pages/Profile/Votes/VoteCard/CaseNumber.tsx (1)

38-44: LGTM! Well-structured component with proper navigation.

The component is well-implemented with proper routing and responsive styling.

web/src/pages/Profile/Stakes/index.tsx (1)

38-44: Add error handling and improve loading states.

The component needs error handling for both queries and loading state for stakingHistoryData.

web/src/pages/Profile/Votes/index.tsx (1)

58-58: Replace hardcoded statistics values.

The statistics values passed to StatsAndFilters are hardcoded.

web/src/pages/Profile/Stakes/StakingHistory.tsx (1)

45-46: Add error handling for data fetching.

The component should handle error states from the data fetching hooks.

web/src/components/DisputeView/PeriodBanner.tsx (2)

62-66: LGTM!

The interface is well-defined with appropriate types and optional flags.


68-77: LGTM!

The function is well-implemented with:

  • Clear return types
  • Comprehensive period handling
  • Theme-based color selection

coderabbitai[bot]
coderabbitai bot previously approved these changes Aug 20, 2025
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

coderabbitai[bot]
coderabbitai bot previously approved these changes Nov 9, 2025
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: 1

♻️ Duplicate comments (2)
web/src/pages/Profile/Stakes/StakingHistory.tsx (1)

48-55: Add error handling for data fetching hooks.

The component should handle error states from both useStakingEventsByCourt and useCourtTree to improve user experience when data fetching fails.

Based on the past review comment, consider:

-  const { data: stakingHistoryData, isFetching: isLoadingStakingHistory } = useStakingEventsByCourt(
+  const { 
+    data: stakingHistoryData, 
+    isFetching: isLoadingStakingHistory,
+    error: stakingHistoryError 
+  } = useStakingEventsByCourt(
     [],
     skip,
     eventsPerPage,
     searchParamAddress
   );

-  const { data: courtTreeData, isLoading: isLoadingCourtTree } = useCourtTree();
+  const { 
+    data: courtTreeData, 
+    isLoading: isLoadingCourtTree,
+    error: courtTreeError 
+  } = useCourtTree();
+
+  if (stakingHistoryError || courtTreeError) {
+    return <StyledTitle>Error loading staking history. Please try again later.</StyledTitle>;
+  }
web/src/pages/Profile/Stakes/index.tsx (1)

39-53: Add error handling for data fetching.

The component should handle error states from useJurorStakeDetailsQuery and useReadSortitionModuleGetJurorBalance to provide feedback when data loading fails.

Based on the past review comment:

 const Stakes: React.FC<IStakes> = ({ searchParamAddress }) => {
-  const { data: currentStakeData, isLoading: isCurrentStakeLoading } = useJurorStakeDetailsQuery(searchParamAddress);
-  const { data: jurorBalance } = useReadSortitionModuleGetJurorBalance({
+  const { 
+    data: currentStakeData, 
+    isLoading: isCurrentStakeLoading,
+    error: stakesError 
+  } = useJurorStakeDetailsQuery(searchParamAddress);
+  
+  const { 
+    data: jurorBalance,
+    error: balanceError 
+  } = useReadSortitionModuleGetJurorBalance({
     args: [searchParamAddress, BigInt(1)],
   });
+
+  if (stakesError || balanceError) {
+    return <div>Error loading stakes data. Please try again later.</div>;
+  }
+  
   const totalAvailableStake = jurorBalance?.[0];
   const lockedStake = jurorBalance?.[1];
🧹 Nitpick comments (2)
web/src/pages/Courts/CourtDetails/LatestStakesByCourt/DisplayStakes/StakeEventCard/DesktopCard.tsx (1)

107-116: Consider extracting the formatDate utility.

The formatDate function is duplicated in both DesktopCard and MobileCard. Extracting it to a shared utility file would reduce duplication and improve maintainability.

Example location: web/src/utils/date.ts

export const formatDate = (timestamp: string): string => {
  const date = new Date(parseInt(timestamp) * 1000);
  return date.toLocaleDateString("en-GB", {
    day: "2-digit",
    month: "2-digit",
    year: "numeric",
    hour: "2-digit",
    minute: "2-digit",
  });
};
web/src/pages/Courts/CourtDetails/LatestStakesByCourt/DisplayStakes/index.tsx (1)

134-145: Consider edge case in infinite scroll condition.

Line 139's condition acc.length % PER_PAGE === 0 assumes that each fetch returns exactly PER_PAGE items. However, after filtering (lines 107-110), the actual number of items added might be less than PER_PAGE, which would break the pagination logic and prevent loading more results.

Consider tracking whether there are more results available from the API:

  useEffect(() => {
    const sentinel = sentinelRef.current;
    if (!sentinel) return;
+   const hasMore = (data?.userStakingEvents?.count ?? 0) > acc.length;
    const obs = new IntersectionObserver(
      ([e]) => {
-       if (e.isIntersecting && !isFetching && acc.length % PER_PAGE === 0) setPage((p) => p + 1);
+       if (e.isIntersecting && !isFetching && hasMore) setPage((p) => p + 1);
      },
      { threshold: 0.1 }
    );
    obs.observe(sentinel);
    return () => obs.disconnect();
- }, [isFetching, acc.length]);
+ }, [isFetching, acc.length, data?.userStakingEvents?.count]);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3342328 and 574f8c9.

📒 Files selected for processing (16)
  • web/src/hooks/useStakingEventsByCourt.ts (1 hunks)
  • web/src/pages/Courts/CourtDetails/JurorsStakedByCourt/DisplayJurors/Header.tsx (1 hunks)
  • web/src/pages/Courts/CourtDetails/JurorsStakedByCourt/DisplayJurors/JurorCard.tsx (3 hunks)
  • web/src/pages/Courts/CourtDetails/JurorsStakedByCourt/index.tsx (1 hunks)
  • web/src/pages/Courts/CourtDetails/LatestStakesByCourt/DisplayStakes/Header.tsx (1 hunks)
  • web/src/pages/Courts/CourtDetails/LatestStakesByCourt/DisplayStakes/Header/DesktopHeader.tsx (1 hunks)
  • web/src/pages/Courts/CourtDetails/LatestStakesByCourt/DisplayStakes/Header/MobileHeader.tsx (1 hunks)
  • web/src/pages/Courts/CourtDetails/LatestStakesByCourt/DisplayStakes/StakeEventCard.tsx (1 hunks)
  • web/src/pages/Courts/CourtDetails/LatestStakesByCourt/DisplayStakes/StakeEventCard/DesktopCard.tsx (1 hunks)
  • web/src/pages/Courts/CourtDetails/LatestStakesByCourt/DisplayStakes/StakeEventCard/MobileCard.tsx (1 hunks)
  • web/src/pages/Courts/CourtDetails/LatestStakesByCourt/DisplayStakes/index.tsx (1 hunks)
  • web/src/pages/Courts/CourtDetails/LatestStakesByCourt/Search.tsx (1 hunks)
  • web/src/pages/Courts/CourtDetails/LatestStakesByCourt/index.tsx (1 hunks)
  • web/src/pages/Courts/CourtDetails/index.tsx (3 hunks)
  • web/src/pages/Profile/Stakes/StakingHistory.tsx (1 hunks)
  • web/src/pages/Profile/Stakes/index.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (9)
📚 Learning: 2024-10-14T13:58:25.708Z
Learnt from: Harman-singh-waraich
Repo: kleros/kleros-v2 PR: 1703
File: web/src/hooks/queries/usePopulatedDisputeData.ts:58-61
Timestamp: 2024-10-14T13:58:25.708Z
Learning: In `web/src/hooks/queries/usePopulatedDisputeData.ts`, the query and subsequent logic only execute when `disputeData.dispute?.arbitrableChainId` and `disputeData.dispute?.externalDisputeId` are defined, so `initialContext` properties based on these values are safe to use without additional null checks.

Applied to files:

  • web/src/hooks/useStakingEventsByCourt.ts
📚 Learning: 2024-11-21T05:47:08.973Z
Learnt from: Harman-singh-waraich
Repo: kleros/kleros-v2 PR: 1755
File: kleros-app/src/lib/atlas/providers/AtlasProvider.tsx:130-144
Timestamp: 2024-11-21T05:47:08.973Z
Learning: In `kleros-app/src/lib/atlas/providers/AtlasProvider.tsx`, it is acceptable to pass `queryClient` as a positional parameter to the `useQuery` hook.

Applied to files:

  • web/src/hooks/useStakingEventsByCourt.ts
📚 Learning: 2024-06-27T10:11:54.861Z
Learnt from: nikhilverma360
Repo: kleros/kleros-v2 PR: 1632
File: web/src/components/DisputeView/DisputeInfo/DisputeInfoList.tsx:37-42
Timestamp: 2024-06-27T10:11:54.861Z
Learning: `useMemo` is used in `DisputeInfoList` to optimize the rendering of `FieldItems` based on changes in `fieldItems`, ensuring that the mapping and truncation operation are only performed when necessary.

Applied to files:

  • web/src/pages/Courts/CourtDetails/LatestStakesByCourt/DisplayStakes/index.tsx
📚 Learning: 2025-05-15T06:50:40.859Z
Learnt from: tractorss
Repo: kleros/kleros-v2 PR: 1982
File: web/src/pages/Resolver/Landing/index.tsx:62-62
Timestamp: 2025-05-15T06:50:40.859Z
Learning: In the Landing component, it's safe to pass `dispute?.dispute?.arbitrated.id as 0x${string}` to `usePopulatedDisputeData` without additional null checks because the hook internally handles undefined parameters through its `isEnabled` flag and won't execute the query unless all required data is available.

Applied to files:

  • web/src/pages/Courts/CourtDetails/LatestStakesByCourt/DisplayStakes/index.tsx
  • web/src/pages/Profile/Stakes/StakingHistory.tsx
📚 Learning: 2024-10-09T10:18:51.089Z
Learnt from: jaybuidl
Repo: kleros/kleros-v2 PR: 1582
File: web-devtools/src/app/(main)/ruler/RulingModes.tsx:179-199
Timestamp: 2024-10-09T10:18:51.089Z
Learning: In `web-devtools/src/app/(main)/ruler/RulingModes.tsx`, the `handleUpdate` function already handles errors via `wrapWithToast`, so additional error handling is unnecessary.

Applied to files:

  • web/src/pages/Profile/Stakes/StakingHistory.tsx
📚 Learning: 2024-10-09T10:22:41.474Z
Learnt from: jaybuidl
Repo: kleros/kleros-v2 PR: 1582
File: web-devtools/src/app/(main)/ruler/SelectArbitrable.tsx:88-90
Timestamp: 2024-10-09T10:22:41.474Z
Learning: Next.js recommends using the `useEffect` hook to set `isClient` and using `suppressHydrationWarning` as a workaround for handling hydration inconsistencies, especially when dealing with data like `knownArbitrables` that may differ between server-side and client-side rendering. This approach is acceptable in TypeScript/React applications, such as in `web-devtools/src/app/(main)/ruler/SelectArbitrable.tsx`.

Applied to files:

  • web/src/pages/Profile/Stakes/StakingHistory.tsx
📚 Learning: 2024-12-06T13:04:50.495Z
Learnt from: kemuru
Repo: kleros/kleros-v2 PR: 1774
File: web/src/components/CasesDisplay/index.tsx:61-61
Timestamp: 2024-12-06T13:04:50.495Z
Learning: In `web/src/components/CasesDisplay/index.tsx`, the variables `numberDisputes` and `numberClosedDisputes` can sometimes be `NaN`, and should default to `0` using logical OR (`||`) to prevent display issues in the `StatsAndFilters` component.

Applied to files:

  • web/src/pages/Profile/Stakes/StakingHistory.tsx
📚 Learning: 2024-11-29T06:23:15.955Z
Learnt from: Harman-singh-waraich
Repo: kleros/kleros-v2 PR: 1762
File: web/src/utils/parseWagmiError.ts:10-17
Timestamp: 2024-11-29T06:23:15.955Z
Learning: In the `web/src/utils/parseWagmiError.ts` file and throughout the codebase, prefer using optional chaining to handle `undefined` or `null` values, including optional arrays, without adding explicit existence or length checks.

Applied to files:

  • web/src/pages/Profile/Stakes/StakingHistory.tsx
📚 Learning: 2024-12-09T12:36:59.441Z
Learnt from: Harman-singh-waraich
Repo: kleros/kleros-v2 PR: 1775
File: web/src/pages/Courts/CourtDetails/StakePanel/StakeWithdrawButton.tsx:0-0
Timestamp: 2024-12-09T12:36:59.441Z
Learning: In the `StakeWithdrawButton` component, the transaction flow logic is tightly linked to component updates, so extracting it into a custom hook does not provide significant benefits.

Applied to files:

  • web/src/pages/Profile/Stakes/index.tsx
🧬 Code graph analysis (10)
web/src/pages/Courts/CourtDetails/LatestStakesByCourt/DisplayStakes/Header.tsx (1)
web/src/pages/Courts/CourtDetails/LatestStakesByCourt/DisplayStakes/Header/DesktopHeader.tsx (1)
  • DesktopHeader (65-78)
web/src/pages/Courts/CourtDetails/LatestStakesByCourt/DisplayStakes/Header/MobileHeader.tsx (1)
web-devtools/src/styles/responsiveSize.ts (1)
  • responsiveSize (9-12)
web/src/pages/Courts/CourtDetails/LatestStakesByCourt/DisplayStakes/index.tsx (6)
web/src/pages/Home/TopJurors/index.tsx (1)
  • ListContainer (34-45)
web-devtools/src/styles/responsiveSize.ts (1)
  • responsiveSize (9-12)
web/src/hooks/queries/useCourtTree.ts (2)
  • CourtTreeQuery (8-8)
  • useCourtTree (39-47)
web/src/hooks/useStakingEventsByCourt.ts (1)
  • useStakingEventsByCourt (30-74)
web/src/utils/findCourtNameById.ts (1)
  • findCourtNameById (3-14)
web/src/components/StyledSkeleton.tsx (1)
  • SkeletonDisputeListItem (39-39)
web/src/pages/Courts/CourtDetails/LatestStakesByCourt/DisplayStakes/StakeEventCard/MobileCard.tsx (3)
web/src/styles/commonStyles.ts (1)
  • hoverShortTransitionTiming (3-5)
web/src/components/InternalLink.tsx (1)
  • InternalLink (4-8)
web/src/utils/index.ts (1)
  • getTxnExplorerLink (13-14)
web/src/pages/Courts/CourtDetails/LatestStakesByCourt/DisplayStakes/Header/DesktopHeader.tsx (2)
web-devtools/src/styles/Theme.tsx (1)
  • theme (3-10)
web-devtools/src/styles/responsiveSize.ts (1)
  • responsiveSize (9-12)
web/src/pages/Courts/CourtDetails/LatestStakesByCourt/index.tsx (2)
web-devtools/src/styles/responsiveSize.ts (1)
  • responsiveSize (9-12)
web/src/utils/getDescriptiveCourtName.ts (1)
  • getDescriptiveCourtName (1-5)
web/src/pages/Courts/CourtDetails/index.tsx (1)
web-devtools/src/styles/responsiveSize.ts (1)
  • responsiveSize (9-12)
web/src/pages/Profile/Stakes/StakingHistory.tsx (5)
web-devtools/src/styles/responsiveSize.ts (1)
  • responsiveSize (9-12)
web/src/hooks/useStakingEventsByCourt.ts (1)
  • useStakingEventsByCourt (30-74)
web/src/hooks/queries/useCourtTree.ts (1)
  • useCourtTree (39-47)
web/src/pages/Profile/Stakes/index.tsx (1)
  • CourtCardsContainer (21-33)
web/src/utils/findCourtNameById.ts (1)
  • findCourtNameById (3-14)
web/src/pages/Profile/Stakes/index.tsx (1)
web/src/hooks/queries/useJurorStakeDetailsQuery.ts (1)
  • useJurorStakeDetailsQuery (24-36)
web/src/pages/Courts/CourtDetails/LatestStakesByCourt/DisplayStakes/StakeEventCard/DesktopCard.tsx (3)
web/src/styles/commonStyles.ts (1)
  • hoverShortTransitionTiming (3-5)
web/src/components/InternalLink.tsx (1)
  • InternalLink (4-8)
web/src/utils/index.ts (1)
  • getTxnExplorerLink (13-14)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Redirect rules - kleros-v2-testnet-devtools
  • GitHub Check: Redirect rules - kleros-v2-neo
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet-devtools
  • GitHub Check: Header rules - kleros-v2-neo
  • GitHub Check: Pages changed - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet-devtools
  • GitHub Check: Pages changed - kleros-v2-neo
  • GitHub Check: hardhat-tests
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Mend Security Check
🔇 Additional comments (15)
web/src/pages/Courts/CourtDetails/JurorsStakedByCourt/DisplayJurors/Header.tsx (1)

13-13: LGTM! Padding adjustment aligns with UI consistency updates.

The padding update is consistent with similar changes across juror-related components in this PR.

web/src/pages/Courts/CourtDetails/LatestStakesByCourt/DisplayStakes/Header/MobileHeader.tsx (1)

1-35: LGTM! Clean mobile header implementation.

The component correctly uses landscapeStyle to hide itself in landscape orientation, which is appropriate for a mobile-specific header.

web/src/pages/Courts/CourtDetails/JurorsStakedByCourt/DisplayJurors/JurorCard.tsx (1)

6-6: LGTM! JurorLink migration and padding update.

The replacement of JurorTitle with JurorLink is consistent with the broader refactor across the codebase. The padding adjustment aligns with UI consistency improvements.

Also applies to: 19-19, 34-34

web/src/pages/Courts/CourtDetails/JurorsStakedByCourt/index.tsx (1)

1-28: LGTM! Import reorganization.

The import reordering is a minor organizational improvement. The removal of top margin is appropriate since the parent StakingSections component now handles spacing.

web/src/pages/Courts/CourtDetails/LatestStakesByCourt/DisplayStakes/Header.tsx (1)

1-9: LGTM! Header wrapper component.

This wrapper provides a single entry point for the header. Given that both MobileHeader and DesktopHeader exist, this pattern likely anticipates future responsive switching logic.

web/src/pages/Courts/CourtDetails/LatestStakesByCourt/DisplayStakes/Header/DesktopHeader.tsx (1)

1-78: LGTM! Well-structured desktop header with helpful tooltip.

The component correctly uses landscapeStyle to show only in landscape orientation. The tooltip on the "Court" label provides valuable context about subcourt staking behavior.

web/src/pages/Courts/CourtDetails/LatestStakesByCourt/index.tsx (1)

1-30: LGTM! Clean component structure.

The component follows the same pattern as JurorsStakedByCourt, which provides consistency. While there's some styling duplication between the two components, the current approach is acceptable given their similar layout requirements.

web/src/pages/Courts/CourtDetails/index.tsx (2)

102-117: LGTM! Well-structured responsive layout container.

The StakingSections component provides a clean responsive layout that stacks components vertically on mobile and arranges them side-by-side in landscape. The flex basis calculation calc(50% - 24px) correctly accounts for the 48px gap.


170-173: LGTM! Clean integration of staking sections.

The two components are properly wrapped in StakingSections with correct prop spreading.

web/src/pages/Courts/CourtDetails/LatestStakesByCourt/DisplayStakes/StakeEventCard.tsx (1)

16-33: LGTM - Clean presentational wrapper.

The component correctly delegates to responsive variants, following a common pattern for cross-device rendering.

web/src/pages/Courts/CourtDetails/LatestStakesByCourt/Search.tsx (1)

31-61: LGTM - Well-implemented debounced search.

The component correctly implements URL-synced search with appropriate debouncing and history management.

web/src/pages/Courts/CourtDetails/LatestStakesByCourt/DisplayStakes/StakeEventCard/DesktopCard.tsx (1)

118-151: LGTM - Clean desktop presentation.

The component correctly handles conditional court linking and formats all data appropriately for desktop display.

web/src/pages/Profile/Stakes/StakingHistory.tsx (1)

59-63: LGTM - Documented client-side sorting.

The comment clearly explains why client-side sorting is necessary, and the implementation correctly sorts by timestamp descending.

web/src/hooks/useStakingEventsByCourt.ts (1)

30-74: LGTM - Well-structured React Query hook.

The hook correctly:

  • Guards execution with isEnabled check for atlasUri
  • Includes all dependencies in the query key for proper cache invalidation
  • Handles the "all courts" case by passing null when courtIds is empty
  • Sets appropriate staleTime for the data
web/src/pages/Courts/CourtDetails/LatestStakesByCourt/DisplayStakes/StakeEventCard/MobileCard.tsx (1)

110-146: LGTM - Clean mobile presentation.

The component correctly renders stake event data in a mobile-friendly vertical layout with appropriate styling and interactions.

Comment on lines +41 to +77
const getAllChildCourtIds = (court: CourtTreeQuery["court"], courtId: string): number[] => {
if (!court) return [];

const ids: number[] = [];

const traverse = (node: CourtTreeQuery["court"]) => {
if (!node) return;
if (node.id === courtId) {
ids.push(parseInt(node.id));
if (node.children) {
node.children.forEach((child) => {
ids.push(parseInt(child.id));
if (child.children) {
child.children.forEach((gc) => {
ids.push(parseInt(gc.id));
if (gc.children) {
gc.children.forEach((ggc) => {
ids.push(parseInt(ggc.id));
if (ggc.children) {
ggc.children.forEach((gggc) => ids.push(parseInt(gggc.id)));
}
});
}
});
}
});
}
return;
}
if (node.children) {
node.children.forEach((child) => traverse(child));
}
};

traverse(court);
return ids;
};
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 | 🟠 Major

Refactor to support arbitrary court tree depth.

The getAllChildCourtIds function has a hardcoded depth limit of 5 levels. This will fail silently if the court hierarchy is deeper, potentially missing stake events from deeply nested courts.

Replace with a proper recursive implementation:

-const getAllChildCourtIds = (court: CourtTreeQuery["court"], courtId: string): number[] => {
-  if (!court) return [];
-
-  const ids: number[] = [];
-
-  const traverse = (node: CourtTreeQuery["court"]) => {
-    if (!node) return;
-    if (node.id === courtId) {
-      ids.push(parseInt(node.id));
-      if (node.children) {
-        node.children.forEach((child) => {
-          ids.push(parseInt(child.id));
-          if (child.children) {
-            child.children.forEach((gc) => {
-              ids.push(parseInt(gc.id));
-              if (gc.children) {
-                gc.children.forEach((ggc) => {
-                  ids.push(parseInt(ggc.id));
-                  if (ggc.children) {
-                    ggc.children.forEach((gggc) => ids.push(parseInt(gggc.id)));
-                  }
-                });
-              }
-            });
-          }
-        });
-      }
-      return;
-    }
-    if (node.children) {
-      node.children.forEach((child) => traverse(child));
-    }
-  };
-
-  traverse(court);
-  return ids;
-};
+const getAllChildCourtIds = (court: CourtTreeQuery["court"], courtId: string): number[] => {
+  if (!court) return [];
+
+  const ids: number[] = [];
+
+  const collectAllDescendants = (node: CourtTreeQuery["court"]) => {
+    ids.push(parseInt(node.id));
+    if (node.children) {
+      node.children.forEach((child) => collectAllDescendants(child));
+    }
+  };
+
+  const findAndCollect = (node: CourtTreeQuery["court"]): boolean => {
+    if (node.id === courtId) {
+      collectAllDescendants(node);
+      return true;
+    }
+    if (node.children) {
+      for (const child of node.children) {
+        if (findAndCollect(child)) return true;
+      }
+    }
+    return false;
+  };
+
+  findAndCollect(court);
+  return ids;
+};
🤖 Prompt for AI Agents
In web/src/pages/Courts/CourtDetails/LatestStakesByCourt/DisplayStakes/index.tsx
around lines 41 to 77, the getAllChildCourtIds function uses a manually unrolled
traversal limited to 5 levels and misses deeper descendants; replace it with a
proper recursive traversal: find the node whose id equals courtId (or start at
root if matching) and then recursively collect its id and all descendant ids by
visiting each child and concatenating results, ensuring you parse ids to numbers
(parseInt) and handle nullable children and nodes; return the accumulated
number[].

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: 4

♻️ Duplicate comments (1)
web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/index.tsx (1)

41-77: Allow arbitrary court-tree depth

getAllChildCourtIds is still manually unrolled to five levels, so any hierarchy deeper than that silently drops descendants and their staking events. Please switch to a proper recursive traversal.

-const getAllChildCourtIds = (court: CourtTreeQuery["court"], courtId: string): number[] => {
-  if (!court) return [];
-
-  const ids: number[] = [];
-
-  const traverse = (node: CourtTreeQuery["court"]) => {
-    if (!node) return;
-    if (node.id === courtId) {
-      ids.push(parseInt(node.id));
-      if (node.children) {
-        node.children.forEach((child) => {
-          ids.push(parseInt(child.id));
-          if (child.children) {
-            child.children.forEach((gc) => {
-              ids.push(parseInt(gc.id));
-              if (gc.children) {
-                gc.children.forEach((ggc) => {
-                  ids.push(parseInt(ggc.id));
-                  if (ggc.children) {
-                    ggc.children.forEach((gggc) => ids.push(parseInt(gggc.id)));
-                  }
-                });
-              }
-            });
-          }
-        });
-      }
-      return;
-    }
-    if (node.children) {
-      node.children.forEach((child) => traverse(child));
-    }
-  };
-
-  traverse(court);
-  return ids;
-};
+const getAllChildCourtIds = (court: CourtTreeQuery["court"], courtId: string): number[] => {
+  if (!court) return [];
+
+  const ids: number[] = [];
+
+  const collectDescendants = (node: CourtTreeQuery["court"]) => {
+    ids.push(parseInt(node.id));
+    node.children?.forEach(collectDescendants);
+  };
+
+  const findAndCollect = (node: CourtTreeQuery["court"]): boolean => {
+    if (node.id === courtId) {
+      collectDescendants(node);
+      return true;
+    }
+    return node.children?.some(findAndCollect) ?? false;
+  };
+
+  findAndCollect(court);
+  return ids;
+};
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 574f8c9 and e6b8207.

📒 Files selected for processing (10)
  • web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/Header.tsx (1 hunks)
  • web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/Header/DesktopHeader.tsx (1 hunks)
  • web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/Header/MobileHeader.tsx (1 hunks)
  • web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/StakeEventCard.tsx (1 hunks)
  • web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/StakeEventCard/DesktopCard.tsx (1 hunks)
  • web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/StakeEventCard/MobileCard.tsx (1 hunks)
  • web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/index.tsx (1 hunks)
  • web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/Search.tsx (1 hunks)
  • web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/index.tsx (1 hunks)
  • web/src/pages/Courts/CourtDetails/index.tsx (3 hunks)
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2024-11-19T17:18:39.007Z
Learnt from: jaybuidl
Repo: kleros/kleros-v2 PR: 1746
File: contracts/config/courts.v2.mainnet-neo.json:3-5
Timestamp: 2024-11-19T17:18:39.007Z
Learning: In `contracts/config/courts.v2.mainnet-neo.json`, the General Court (id: 1) intentionally references itself as its parent (`"parent": 1`). This self-reference is acceptable and should not be flagged as an issue in future reviews.

Applied to files:

  • web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/index.tsx
📚 Learning: 2024-11-19T16:31:08.965Z
Learnt from: jaybuidl
Repo: kleros/kleros-v2 PR: 1746
File: contracts/config/courts.v2.mainnet-neo.json:167-170
Timestamp: 2024-11-19T16:31:08.965Z
Learning: In the court hierarchy, child courts' `minStake` must be greater than or equal to their parent court's `minStake`.

Applied to files:

  • web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/index.tsx
📚 Learning: 2024-11-19T16:09:41.467Z
Learnt from: jaybuidl
Repo: kleros/kleros-v2 PR: 1746
File: contracts/config/courts.v2.mainnet-neo.json:3-17
Timestamp: 2024-11-19T16:09:41.467Z
Learning: In `contracts/config/courts.v2.mainnet-neo.json`, the General Court (ID: 1) can have its `parent` ID set to itself (`"parent": 1`), as there is no parent court with ID 0 currently.

Applied to files:

  • web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/index.tsx
📚 Learning: 2024-06-27T10:11:54.861Z
Learnt from: nikhilverma360
Repo: kleros/kleros-v2 PR: 1632
File: web/src/components/DisputeView/DisputeInfo/DisputeInfoList.tsx:37-42
Timestamp: 2024-06-27T10:11:54.861Z
Learning: `useMemo` is used in `DisputeInfoList` to optimize the rendering of `FieldItems` based on changes in `fieldItems`, ensuring that the mapping and truncation operation are only performed when necessary.

Applied to files:

  • web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/index.tsx
📚 Learning: 2025-05-15T06:50:40.859Z
Learnt from: tractorss
Repo: kleros/kleros-v2 PR: 1982
File: web/src/pages/Resolver/Landing/index.tsx:62-62
Timestamp: 2025-05-15T06:50:40.859Z
Learning: In the Landing component, it's safe to pass `dispute?.dispute?.arbitrated.id as 0x${string}` to `usePopulatedDisputeData` without additional null checks because the hook internally handles undefined parameters through its `isEnabled` flag and won't execute the query unless all required data is available.

Applied to files:

  • web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/index.tsx
🧬 Code graph analysis (8)
web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/Header/MobileHeader.tsx (2)
web-devtools/src/styles/Theme.tsx (1)
  • theme (3-10)
web-devtools/src/styles/responsiveSize.ts (1)
  • responsiveSize (9-12)
web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/StakeEventCard/MobileCard.tsx (3)
web/src/styles/commonStyles.ts (1)
  • hoverShortTransitionTiming (3-5)
web/src/components/InternalLink.tsx (1)
  • InternalLink (4-8)
web/src/utils/index.ts (1)
  • getTxnExplorerLink (13-14)
web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/StakeEventCard/DesktopCard.tsx (3)
web/src/styles/commonStyles.ts (1)
  • hoverShortTransitionTiming (3-5)
web/src/components/InternalLink.tsx (1)
  • InternalLink (4-8)
web/src/utils/index.ts (1)
  • getTxnExplorerLink (13-14)
web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/Header.tsx (1)
web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/Header/DesktopHeader.tsx (1)
  • DesktopHeader (65-78)
web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/Header/DesktopHeader.tsx (1)
web-devtools/src/styles/responsiveSize.ts (1)
  • responsiveSize (9-12)
web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/index.tsx (6)
web/src/pages/Home/TopJurors/index.tsx (1)
  • ListContainer (34-45)
web-devtools/src/styles/responsiveSize.ts (1)
  • responsiveSize (9-12)
web/src/hooks/queries/useCourtTree.ts (2)
  • CourtTreeQuery (8-8)
  • useCourtTree (39-47)
web/src/hooks/useStakingEventsByCourt.ts (1)
  • useStakingEventsByCourt (30-74)
web/src/utils/findCourtNameById.ts (1)
  • findCourtNameById (3-14)
web/src/components/StyledSkeleton.tsx (1)
  • SkeletonDisputeListItem (39-39)
web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/index.tsx (2)
web-devtools/src/styles/responsiveSize.ts (1)
  • responsiveSize (9-12)
web/src/utils/getDescriptiveCourtName.ts (1)
  • getDescriptiveCourtName (1-5)
web/src/pages/Courts/CourtDetails/index.tsx (1)
web-devtools/src/styles/responsiveSize.ts (1)
  • responsiveSize (9-12)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (15)
  • GitHub Check: Redirect rules - kleros-v2-neo
  • GitHub Check: Redirect rules - kleros-v2-testnet-devtools
  • GitHub Check: Header rules - kleros-v2-neo
  • GitHub Check: Header rules - kleros-v2-testnet-devtools
  • GitHub Check: Pages changed - kleros-v2-neo
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Redirect rules - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet-devtools
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Header rules - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet
  • GitHub Check: Pages changed - kleros-v2-testnet
  • GitHub Check: Analyze (javascript)
  • GitHub Check: hardhat-tests
  • GitHub Check: Mend Security Check

Comment on lines +112 to +178
useEffect(() => {
const allItems = data?.userStakingEvents?.items ?? [];

const filteredItems = allItems.filter((item) => {
const itemCourtId = item.item.args._courtID;
return courtIds.includes(parseInt(itemCourtId));
});

const chunk = filteredItems.map((item) => {
const itemCourtId = item.item.args._courtID;
const courtName = findCourtNameById(courtTree, itemCourtId);

return {
id: item.item.id,
address: item.item.args._address,
stake: item.item.args._amount,
timestamp: item.item.blockTimestamp,
transactionHash: item.item.transactionHash,
courtId: parseInt(itemCourtId),
courtName: courtName ?? `Court #${itemCourtId}`,
};
});

const sortedChunk = chunk.sort((a, b) => parseInt(b.timestamp) - parseInt(a.timestamp));

if (sortedChunk.length) setAcc((prev) => [...prev, ...sortedChunk]);
}, [data, courtIds, courtTree]);

const sentinelRef = useRef<HTMLDivElement | null>(null);

useEffect(() => {
const sentinel = sentinelRef.current;
if (!sentinel) return;
const obs = new IntersectionObserver(
([e]) => {
if (e.isIntersecting && !isFetching && acc.length % PER_PAGE === 0) setPage((p) => p + 1);
},
{ threshold: 0.1 }
);
obs.observe(sentinel);
return () => obs.disconnect();
}, [isFetching, acc.length]);

const stakes = useMemo(() => acc, [acc]);

return (
<>
{!isUndefined(stakes) && stakes.length === 0 && !isFetching ? (
<StyledLabel>No stakes found</StyledLabel>
) : (
<ListContainer>
<Header />
<CardsWrapper>
{stakes.map((s) => (
<StakeEventCard
key={s.id}
address={s.address}
stake={s.stake}
timestamp={s.timestamp}
transactionHash={s.transactionHash}
courtName={s.courtName}
courtId={s.courtId}
currentCourtId={courtId ? parseInt(courtId) : undefined}
/>
))}
{isFetching && [...Array(9)].map((_, i) => <SkeletonDisputeListItem key={`s-${i}`} />)}
<div ref={sentinelRef} />
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 | 🟠 Major

Stop duplicating events on query refetches

When React Query refetches the same page (focus/reconnect, cache refresh), sortedChunk contains the same items as before and setAcc([...prev, ...sortedChunk]) appends duplicates, inflating the list. Please merge by id instead of blind concatenation.

-    if (sortedChunk.length) setAcc((prev) => [...prev, ...sortedChunk]);
+    if (sortedChunk.length) {
+      setAcc((prev) => {
+        const seen = new Set(prev.map((item) => item.id));
+        const next = sortedChunk.filter((item) => !seen.has(item.id));
+        return next.length ? [...prev, ...next] : prev;
+      });
+    }
📝 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
useEffect(() => {
const allItems = data?.userStakingEvents?.items ?? [];
const filteredItems = allItems.filter((item) => {
const itemCourtId = item.item.args._courtID;
return courtIds.includes(parseInt(itemCourtId));
});
const chunk = filteredItems.map((item) => {
const itemCourtId = item.item.args._courtID;
const courtName = findCourtNameById(courtTree, itemCourtId);
return {
id: item.item.id,
address: item.item.args._address,
stake: item.item.args._amount,
timestamp: item.item.blockTimestamp,
transactionHash: item.item.transactionHash,
courtId: parseInt(itemCourtId),
courtName: courtName ?? `Court #${itemCourtId}`,
};
});
const sortedChunk = chunk.sort((a, b) => parseInt(b.timestamp) - parseInt(a.timestamp));
if (sortedChunk.length) setAcc((prev) => [...prev, ...sortedChunk]);
}, [data, courtIds, courtTree]);
const sentinelRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
const sentinel = sentinelRef.current;
if (!sentinel) return;
const obs = new IntersectionObserver(
([e]) => {
if (e.isIntersecting && !isFetching && acc.length % PER_PAGE === 0) setPage((p) => p + 1);
},
{ threshold: 0.1 }
);
obs.observe(sentinel);
return () => obs.disconnect();
}, [isFetching, acc.length]);
const stakes = useMemo(() => acc, [acc]);
return (
<>
{!isUndefined(stakes) && stakes.length === 0 && !isFetching ? (
<StyledLabel>No stakes found</StyledLabel>
) : (
<ListContainer>
<Header />
<CardsWrapper>
{stakes.map((s) => (
<StakeEventCard
key={s.id}
address={s.address}
stake={s.stake}
timestamp={s.timestamp}
transactionHash={s.transactionHash}
courtName={s.courtName}
courtId={s.courtId}
currentCourtId={courtId ? parseInt(courtId) : undefined}
/>
))}
{isFetching && [...Array(9)].map((_, i) => <SkeletonDisputeListItem key={`s-${i}`} />)}
<div ref={sentinelRef} />
useEffect(() => {
const allItems = data?.userStakingEvents?.items ?? [];
const filteredItems = allItems.filter((item) => {
const itemCourtId = item.item.args._courtID;
return courtIds.includes(parseInt(itemCourtId));
});
const chunk = filteredItems.map((item) => {
const itemCourtId = item.item.args._courtID;
const courtName = findCourtNameById(courtTree, itemCourtId);
return {
id: item.item.id,
address: item.item.args._address,
stake: item.item.args._amount,
timestamp: item.item.blockTimestamp,
transactionHash: item.item.transactionHash,
courtId: parseInt(itemCourtId),
courtName: courtName ?? `Court #${itemCourtId}`,
};
});
const sortedChunk = chunk.sort((a, b) => parseInt(b.timestamp) - parseInt(a.timestamp));
if (sortedChunk.length) {
setAcc((prev) => {
const seen = new Set(prev.map((item) => item.id));
const next = sortedChunk.filter((item) => !seen.has(item.id));
return next.length ? [...prev, ...next] : prev;
});
}
}, [data, courtIds, courtTree]);
const sentinelRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
const sentinel = sentinelRef.current;
if (!sentinel) return;
const obs = new IntersectionObserver(
([e]) => {
if (e.isIntersecting && !isFetching && acc.length % PER_PAGE === 0) setPage((p) => p + 1);
},
{ threshold: 0.1 }
);
obs.observe(sentinel);
return () => obs.disconnect();
}, [isFetching, acc.length]);
const stakes = useMemo(() => acc, [acc]);
return (
<>
{!isUndefined(stakes) && stakes.length === 0 && !isFetching ? (
<StyledLabel>No stakes found</StyledLabel>
) : (
<ListContainer>
<Header />
<CardsWrapper>
{stakes.map((s) => (
<StakeEventCard
key={s.id}
address={s.address}
stake={s.stake}
timestamp={s.timestamp}
transactionHash={s.transactionHash}
courtName={s.courtName}
courtId={s.courtId}
currentCourtId={courtId ? parseInt(courtId) : undefined}
/>
))}
{isFetching && [...Array(9)].map((_, i) => <SkeletonDisputeListItem key={`s-${i}`} />)}
<div ref={sentinelRef} />
🤖 Prompt for AI Agents
In
web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/index.tsx
around lines 112-178, the effect appends sortedChunk to acc on every refetch
which produces duplicate events; instead merge by id and deduplicate before
updating state. Change the setAcc step to merge previous acc and sortedChunk
into a single array, remove duplicates by id (e.g., build a Map keyed by item.id
to keep a single entry per id), then sort the merged unique list by timestamp
(desc) and call setAcc with that deduped, sorted array so refetches don't append
duplicates.

Comment on lines +107 to +115
const formatDate = (timestamp: string): string => {
const date = new Date(parseInt(timestamp) * 1000);
return date.toLocaleDateString("en-GB", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
});
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 | 🟠 Major

Show both date and time in desktop staking history

The desktop card also relies on toLocaleDateString, which ignores hour/minute and hides the event time. Use toLocaleString (or a formatter that respects time options) so timestamps retain their time component.

-  return date.toLocaleDateString("en-GB", {
+  return date.toLocaleString("en-GB", {
     day: "2-digit",
     month: "2-digit",
     year: "numeric",
     hour: "2-digit",
     minute: "2-digit",
   });
📝 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 formatDate = (timestamp: string): string => {
const date = new Date(parseInt(timestamp) * 1000);
return date.toLocaleDateString("en-GB", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
});
const formatDate = (timestamp: string): string => {
const date = new Date(parseInt(timestamp) * 1000);
return date.toLocaleString("en-GB", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
});
🤖 Prompt for AI Agents
In
web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/StakeEventCard/DesktopCard.tsx
around lines 107 to 115, the date formatter uses toLocaleDateString which
ignores hour/minute options and drops the event time; change it to
toLocaleString (or another formatter that respects time options) while keeping
the same "en-GB" locale and options (day, month, year, hour, minute) so the
returned string includes both date and time; ensure timestamp parsing remains
correct (parseInt(timestamp) * 1000) and update any tests or usages expecting
the old format if necessary.

Comment on lines +99 to +107
const formatDate = (timestamp: string): string => {
const date = new Date(parseInt(timestamp) * 1000);
return date.toLocaleDateString("en-GB", {
day: "2-digit",
month: "2-digit",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
});
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 | 🟠 Major

Ensure time appears in staking history timestamps

toLocaleDateString drops the hour and minute options, so only the date renders and the time is silently lost. Readers can’t see when the stake event happened. Swap to toLocaleString (or another formatter that supports time fields) so both date and time are shown.

-  return date.toLocaleDateString("en-GB", {
+  return date.toLocaleString("en-GB", {
     day: "2-digit",
     month: "2-digit",
     year: "numeric",
     hour: "2-digit",
     minute: "2-digit",
   });
🤖 Prompt for AI Agents
In
web/src/pages/Courts/CourtDetails/StakingHistoryByCourt/DisplayStakes/StakeEventCard/MobileCard.tsx
around lines 99 to 107, the formatter uses toLocaleDateString which ignores
hour/minute options so time is not shown; change to use toLocaleString (or
another formatter that supports time) when formatting the Date (e.g.,
date.toLocaleString("en-GB", { day: "2-digit", month: "2-digit", year:
"numeric", hour: "2-digit", minute: "2-digit" })) so both date and time are
rendered, keeping the existing timestamp parsing (parseInt(timestamp) * 1000).

Comment on lines +35 to +58
const initial = searchParams.get("stakeSearch") ?? "";
const [value, setValue] = useState(initial);
useDebounce(
() => {
const params = new URLSearchParams(searchParams);
if (isEmpty(value)) {
params.delete("stakeSearch");
} else {
params.set("stakeSearch", value);
}
navigate(`${pathname}?${params.toString()}`, { replace: true });
},
500,
[value]
);
return (
<Container>
<StyledSearchbar
dir="auto"
type="text"
placeholder="Search by address"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
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 | 🟠 Major

Keep the search field in sync with URL changes

value is set only once from stakeSearch, so if the user navigates with browser back/forward or another component mutates the query string, the input keeps the old value and the debounce will re-push stale params. Wire the state back to the current search params before debouncing.

-import React, { useState } from "react";
+import React, { useEffect, useState } from "react";
@@
-  const initial = searchParams.get("stakeSearch") ?? "";
-  const [value, setValue] = useState(initial);
+  const initial = searchParams.get("stakeSearch") ?? "";
+  const [value, setValue] = useState(initial);
+
+  useEffect(() => {
+    setValue(initial);
+  }, [initial]);
📝 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 initial = searchParams.get("stakeSearch") ?? "";
const [value, setValue] = useState(initial);
useDebounce(
() => {
const params = new URLSearchParams(searchParams);
if (isEmpty(value)) {
params.delete("stakeSearch");
} else {
params.set("stakeSearch", value);
}
navigate(`${pathname}?${params.toString()}`, { replace: true });
},
500,
[value]
);
return (
<Container>
<StyledSearchbar
dir="auto"
type="text"
placeholder="Search by address"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
const initial = searchParams.get("stakeSearch") ?? "";
const [value, setValue] = useState(initial);
useEffect(() => {
setValue(initial);
}, [initial]);
useDebounce(
() => {
const params = new URLSearchParams(searchParams);
if (isEmpty(value)) {
params.delete("stakeSearch");
} else {
params.set("stakeSearch", value);
}
navigate(`${pathname}?${params.toString()}`, { replace: true });
},
500,
[value]
);
return (
<Container>
<StyledSearchbar
dir="auto"
type="text"
placeholder="Search by address"
value={value}
onChange={(e) => setValue(e.target.value)}
/>

@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

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