diff --git a/src/components/common/TopEarners/index.tsx b/src/components/common/TopEarners/index.tsx new file mode 100644 index 00000000..42889b7c --- /dev/null +++ b/src/components/common/TopEarners/index.tsx @@ -0,0 +1,118 @@ +import { EuiLoadingSpinner, EuiText } from '@elastic/eui'; +import { colors, mobileBreakpoint } from 'config'; +import { observer } from 'mobx-react-lite'; +import React, { useEffect, useState } from 'react'; +import { useStores } from 'store'; +import styled from 'styled-components'; +import { LeaerboardItem } from './leaderboardItem'; + +interface TopEarnersProps { + limit?: number; + className?: string; + style?: React.CSSProperties; + onError?: (error: Error) => void; +} + +const Container = styled.div` + height: 100%; + min-height: 100%; + overflow: auto; + align-items: center; + justify-content: center; + min-width: 100%; + & > .inner { + position: relative; + margin: auto; + max-width: 100%; + min-width: 100%; + display: flex; + flex-direction: column; + gap: 1rem; + } + + & .summary { + position: absolute; + right: 0; + top: 0; + } + + @media (${mobileBreakpoint}) { + height: calc(100% - 2rem); + padding: 1rem; + & > .inner { + max-width: 100%; + min-width: 300px; + } + & .summary { + position: relative; + right: 0; + top: 0; + } + } +`; + +const LoaderContainer = styled.div` + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; +`; + +const ErrorContainer = styled.div` + display: flex; + justify-content: center; + align-items: center; + width: 100%; + padding: 1rem; + color: ${colors.light.red1}; +`; + +const TopEarners = observer(({ limit = 5, className, style, onError }: TopEarnersProps) => { + const { leaderboard } = useStores(); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchData = async () => { + try { + await leaderboard.fetchLeaders(); + } catch (err) { + const error = err instanceof Error ? err : new Error('Failed to fetch leaderboard data'); + setError(error); + onError?.(error); + } + }; + + fetchData(); + }, [leaderboard, onError]); + + if (error) { + return ( + + Failed to load top earners + + ); + } + + if (leaderboard.isLoading) { + return ( + + + + ); + } + + return ( + +
+ {leaderboard?.topEarners + .slice(0, limit) + .map((item: any, index: number) => ( + + ))} +
+
+ ); +}); + +export default TopEarners; diff --git a/src/components/common/TopEarners/leaderboardItem/index.tsx b/src/components/common/TopEarners/leaderboardItem/index.tsx new file mode 100644 index 00000000..f75a5d47 --- /dev/null +++ b/src/components/common/TopEarners/leaderboardItem/index.tsx @@ -0,0 +1,81 @@ +import { EuiText } from '@elastic/eui'; +import React from 'react'; +import styled from 'styled-components'; +import { PriceOuterContainer } from '../..'; +import { colors } from '../../../../config'; +import { DollarConverter } from '../../../../helpers'; +import { LeaderItem } from '../../../../pages/leaderboard/store'; +import { UserInfo } from '../../../../pages/leaderboard/userInfo'; + +const ItemContainer = styled.div` + --position-gutter: 3rem; + position: sticky; + display: flex; + flex-direction: row; + align-items: center; + gap: 1rem; + padding: 0.5rem; + margin-left: var(--position-gutter); + background-color: ${colors.light.pureWhite}; + border-radius: 0.5rem; + border: 1px solid transparent; + transition-property: border box-shadow; + transition-timing-function: ease; + transition-duration: 0.2s; + &:hover { + border: 1px solid ${colors.light.borderGreen1}; + box-shadow: 0 0 5px 1px ${colors.light.borderGreen2}; + } + + & .userSummary { + margin-left: auto; + display: flex; + align-items: center; + gap: 1rem; + } + + & .USD_Price { + font-size: 1rem; + text-align: right; + .currency { + font-size: 0.8em; + } + } + + & .position { + position: absolute; + left: calc(-1 * var(--position-gutter)); + font-weight: 500; + } +`; + +type Props = LeaderItem & { + position: number; + owner_pubkey: string; + total_sats_earned: number; +}; + +const color = colors.light; +export const LeaerboardItem = ({ owner_pubkey, total_sats_earned, position }: Props) => ( + + + #{position} + + +
+
+ +
+ {DollarConverter(total_sats_earned)} +
+
+ SAT +
+
+
+
+
+); diff --git a/src/pages/BountiesLandingPage/index.tsx b/src/pages/BountiesLandingPage/index.tsx index fb35edbb..d7dbcaac 100644 --- a/src/pages/BountiesLandingPage/index.tsx +++ b/src/pages/BountiesLandingPage/index.tsx @@ -5,6 +5,7 @@ import { useIsMobile } from '../../hooks'; import { colors } from '../../config/colors'; import { BountiesHeader, HeaderWrap, Leftheader } from '../tickets/style.ts'; import { BountyHeaderContent } from '../tickets/workspace/workspaceHeader/WorkspaceHeaderStyles.tsx'; +import TopEarners from '../../components/common/TopEarners/index.tsx'; const BountiesLandingPage: React.FC = () => { const isMobile = useIsMobile(); @@ -28,7 +29,7 @@ const BountiesLandingPage: React.FC = () => { min-height: 500px; margin: 30px auto; width: 100%; - padding: 40px 30px; + padding: 40px 20px 20px 30px; background: white; `; @@ -42,7 +43,7 @@ const BountiesLandingPage: React.FC = () => { &:after { content: ''; position: absolute; - left: 66.66%; + left: 63%; top: 0; bottom: 0; width: 1px; @@ -70,7 +71,7 @@ const BountiesLandingPage: React.FC = () => { font-size: 24px; font-family: Barlow; color: ${color.text1}; - margin-bottom: 32px; + margin-bottom: 24px; font-weight: 500; } @@ -105,7 +106,7 @@ const BountiesLandingPage: React.FC = () => {

Freedom to Earn!

-

Second column with content

+
diff --git a/src/pages/leaderboard/store.ts b/src/pages/leaderboard/store.ts index 4d975533..e12ad4ec 100644 --- a/src/pages/leaderboard/store.ts +++ b/src/pages/leaderboard/store.ts @@ -52,6 +52,10 @@ export class LeaderboardStore { get others() { return this.sortedBySats.slice(3); } + + get topEarners() { + return this.sortedBySats; + } } export const leaderboardStore = new LeaderboardStore();