Skip to content

Commit

Permalink
Merge pull request #178 from dappforce/deploy/leaderboard
Browse files Browse the repository at this point in the history
Top Users
  • Loading branch information
olehmell authored Jan 19, 2024
2 parents cd232c5 + f2099e8 commit 229c78b
Show file tree
Hide file tree
Showing 29 changed files with 603 additions and 100 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
"bignumber.js": "^9.0.1",
"bn.js": "5.2.1",
"clsx": "^1.1.1",
"compressorjs": "^1.2.1",
"copy-to-clipboard": "^3.3.1",
"dayjs": "^1.9.6",
"dotenv-webpack": "^1.0.2",
Expand Down
39 changes: 39 additions & 0 deletions src/components/creators/MobileActiveStakingSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ComponentProps } from 'react'
import { useIsMobileWidthOrDevice } from '../responsive'
import MobileStakerRewardDashboard from './MobileStakerRewardDashboard'
import TopUsersCard from './TopUsersCard'

export type MobileActiveStakingSectionProps = ComponentProps<'div'> & {
offsetX?: number
offsetY?: number
showTopUsers?: boolean
}

export default function MobileActiveStakingSection({
offsetX = -16,
offsetY = -12,
showTopUsers = true,
...props
}: MobileActiveStakingSectionProps) {
const isMobile = useIsMobileWidthOrDevice()
if (!isMobile) return null

return (
<>
<div
{...props}
style={{
margin: `${offsetY}px ${offsetX}px 0`,
position: 'sticky',
top: '64px',
zIndex: 10,
background: 'white',
...props.style,
}}
>
<MobileStakerRewardDashboard />
</div>
{showTopUsers && <TopUsersCard style={{ margin: `0 ${offsetX}px`, background: 'white' }} />}
</>
)
}
15 changes: 8 additions & 7 deletions src/components/creators/MobileStakerRewardDashboard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Tooltip } from 'antd'
import { Button, Tooltip } from 'antd'
import clsx from 'clsx'
import Link from 'next/link'
import { ComponentProps, useState } from 'react'
Expand All @@ -18,12 +18,12 @@ import StakerRewardProgressBar from './staker-rewards/StakerRewardProgressBar'
export type MobileStakerRewardDashboardProps = ComponentProps<'div'>

export default function MobileStakerRewardDashboard(props: MobileStakerRewardDashboardProps) {
const { data: rewardReport, loading } = useFetchUserRewardReport()
const likesCount = rewardReport?.superLikesCount ?? 0
const myAddress = useMyAddress() ?? ''
const { data: totalStake, loading } = useFetchTotalStake(myAddress)

if (loading) return null

if (!likesCount) {
if (!totalStake?.hasStaked) {
return <StakeSubBanner {...props} />
}
return <StakerRewardDashboard {...props} />
Expand All @@ -36,22 +36,23 @@ function StakeSubBanner(props: MobileStakerRewardDashboardProps) {
{...props}
className={clsx(props.className, styles.MobileStakerRewardDashboard, styles.StakeSubBanner)}
>
<div className={clsx(styles.Summary)}>
<div className={clsx(styles.Summary, 'py-2')}>
<div className={styles.Content}>
<span className={clsx('d-flex GapTiny align-items-center')}>
<span className='FontWeightSemibold'>Stake SUB and earn more</span>
</span>
<div className={clsx('d-flex align-items-center GapTiny')}>
<Link passHref href={getSubIdCreatorsLink()}>
<a
<Button
type='primary'
className='FontWeightSemibold'
target='_blank'
onClick={() =>
sendEvent('astake_banner_add_stake', { eventSource: 'mobile-staker-banner' })
}
>
Stake SUB
</a>
</Button>
</Link>
</div>
</div>
Expand Down
175 changes: 175 additions & 0 deletions src/components/creators/TopUsersCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import { Skeleton } from 'antd'
import clsx from 'clsx'
import { ComponentProps, CSSProperties } from 'react'
import { useSelectProfileSpace, useSelectSpace } from 'src/rtk/app/hooks'
import { useFetchTopUsers } from 'src/rtk/features/activeStaking/hooks'
import { useIsMobileWidthOrDevice } from '../responsive'
import { SpaceAvatar } from '../spaces/helpers'
import ViewSpaceLink from '../spaces/ViewSpaceLink'
import { MutedSpan } from '../utils/MutedText'
import Segment from '../utils/Segment'

export type TopUsersCardProps = ComponentProps<'div'>

export default function TopUsersCard({ ...props }: TopUsersCardProps) {
const { data, loading } = useFetchTopUsers()
const isMobile = useIsMobileWidthOrDevice()

const isLoading = loading || !data

// const seeMoreButton = (
// <Button
// type='primary'
// ghost
// className='p-0 GapMini d-flex align-items-center'
// style={{ height: 'auto', border: 'none', boxShadow: 'none' }}
// >
// <span>See more</span>
// <IoChevronForward />
// </Button>
// )

const content = isLoading ? (
<Skeleton />
) : (
<>
<div className='d-flex justify-content-between align-items-center'>
<div className='d-flex align-items-center FontWeightSemibold GapMini'>
<span className='FontSemilarge'>Top users (last 24h)</span>
</div>
{/* {isMobile && seeMoreButton} */}
</div>
<div
className={clsx('mt-2', isMobile && 'GapNormal')}
style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr 1fr' : '1fr' }}
>
<div className='d-flex flex-column FontSmall' style={{ minWidth: 0 }}>
<MutedSpan className='FontWeightMedium mb-1'>Stakers</MutedSpan>
<div className='d-flex flex-column GapTiny'>
{data.stakers.map((staker, i) => (
<UserInfo rank={i + 1} key={i} user={staker} />
))}
</div>
</div>
<div
className={clsx('d-flex flex-column FontSmall', !isMobile && 'mt-3 pt-2')}
style={{ borderTop: !isMobile ? '1px solid #E2E8F0' : 'none', minWidth: 0 }}
>
<MutedSpan className='FontWeightMedium mb-1'>Creators</MutedSpan>
<div className='d-flex flex-column GapTiny'>
{data?.creators.map((creator, i) => (
<UserInfo rank={i + 1} key={i} user={creator} />
))}
</div>
</div>
</div>
{/* {!isMobile && <div className='d-flex justify-content-center mt-2'>{seeMoreButton}</div>} */}
</>
)

if (isMobile) {
return (
<div {...props} className={clsx(props.className, 'p-3 pt-2.5')}>
{content}
</div>
)
}

return (
<Segment
{...props}
style={{ background: 'white', ...props.style }}
className={clsx(props.className, 'p-3')}
>
{content}
</Segment>
)
}

function UserInfo({
rank,
user,
}: {
rank: number
user: { address: string; superLikesCount: number }
}) {
const profile = useSelectProfileSpace(user.address)
const space = useSelectSpace(profile?.spaceId)
if (!space) return null

return (
<div className='d-flex align-items-center'>
<div className='position-relative'>
<SpaceAvatar space={space.struct} avatar={space.content?.image} size={34} />
{[1, 2, 3].includes(rank) && (
<Medal
className='position-absolute FontTiny'
style={{ bottom: -2, right: 6 }}
rank={rank as 1 | 2 | 3}
/>
)}
</div>
<div className='d-flex flex-column' style={{ minWidth: 0 }}>
<ViewSpaceLink
title={
<span
className='FontWeightMedium FontNormal'
style={{
minWidth: 0,
textOverflow: 'ellipsis',
overflow: 'hidden',
whiteSpace: 'nowrap',
position: 'relative',
top: '2px',
}}
>
{space.content?.name ?? 'Unnamed'}
</span>
}
space={space.struct}
/>
<div className='d-flex align-items-center ColorMuted GapMini'>
<span>{user.superLikesCount} Likes</span>
</div>
</div>
</div>
)
}

function Medal({ rank, ...props }: ComponentProps<'div'> & { rank: 1 | 2 | 3 }) {
const rankStyles: Record<number, CSSProperties> = {
1: {
backgroundColor: '#FCDF40',
color: '#887304',
},
2: {
backgroundColor: '#D4D4D4',
color: '#8C8C8C',
},
3: {
backgroundColor: '#DEA368',
color: '#9B5E23',
},
}

const style: CSSProperties = {
...rankStyles[rank],
border: '1px solid white',
width: '1rem',
height: '1rem',
borderRadius: '50%',
}

return (
<div
{...props}
style={{ ...style, ...props.style }}
className={clsx(
'FontWeightMedium d-flex align-items-center justify-content-center',
props.className,
)}
>
<span>{rank}</span>
</div>
)
}
13 changes: 6 additions & 7 deletions src/components/main/HomePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import { GET_TOTAL_COUNTS } from 'src/graphql/queries'
import { GetHomePageData } from 'src/graphql/__generated__/GetHomePageData'
import { useSendEvent } from 'src/providers/AnalyticContext'
import { getInitialPropsWithRedux } from 'src/rtk/app'
import { fetchTopUsersWithSpaces } from 'src/rtk/features/activeStaking/topUsersSlice'
import { useFetchTotalStake } from 'src/rtk/features/creators/totalStakeHooks'
import { PostKind } from 'src/types/graphql-global-types'
import { getAmountRange } from 'src/utils/analytics'
import { useIsSignedIn, useMyAddress } from '../auth/MyAccountsContext'
import { CreatorDashboardHomeVariant } from '../creators/CreatorDashboardSidebar'
import MobileStakerRewardDashboard from '../creators/MobileStakerRewardDashboard'
import MobileActiveStakingSection from '../creators/MobileActiveStakingSection'
import { useIsMobileWidthOrDevice } from '../responsive'
import { CreatorsSpaces } from '../spaces/LatestSpacesPage'
import Section from '../utils/Section'
Expand Down Expand Up @@ -185,11 +186,7 @@ const TabsHomePage = ({

return (
<>
{isMobile && (
<MobileStakerRewardDashboard
style={{ margin: '-12px -16px 0', position: 'sticky', top: '64px', zIndex: 10 }}
/>
)}
<MobileActiveStakingSection />
<span>
{!isMobile && <AffixTabs tabKey={tab} setKey={onChangeKey} visible={hidden} {...props} />}
</span>
Expand Down Expand Up @@ -228,7 +225,7 @@ const HomePage: NextPage<Props> = props => {
)
}

getInitialPropsWithRedux(HomePage, async ({ apolloClient }) => {
getInitialPropsWithRedux(HomePage, async ({ apolloClient, subsocial, dispatch }) => {
const apolloRes = await apolloClient?.query<GetHomePageData>({
query: GET_TOTAL_COUNTS,
})
Expand All @@ -244,6 +241,8 @@ getInitialPropsWithRedux(HomePage, async ({ apolloClient }) => {
totalSpaceCount = spaceCount.totalCount
}

await fetchTopUsersWithSpaces(dispatch, subsocial)

return {
totalPostCount,
totalSpaceCount,
Expand Down
Loading

0 comments on commit 229c78b

Please sign in to comment.