stacker.news/pages/rewards/index.js

217 lines
6.0 KiB
JavaScript
Raw Normal View History

import { gql } from 'graphql-tag'
2023-07-24 18:35:05 +00:00
import Button from 'react-bootstrap/Button'
import InputGroup from 'react-bootstrap/InputGroup'
import { getGetServerSideProps } from '@/api/ssrApollo'
import { Form, Input, SubmitButton } from '@/components/form'
import Layout from '@/components/layout'
2022-12-08 00:04:02 +00:00
import { useMutation, useQuery } from '@apollo/client'
import Link from 'next/link'
import { amountSchema } from '@/lib/validate'
import { numWithUnits } from '@/lib/format'
import PageLoading from '@/components/page-loading'
import { useShowModal } from '@/components/modal'
2023-07-24 22:50:12 +00:00
import dynamic from 'next/dynamic'
import { FAST_POLL_INTERVAL, SSR } from '@/lib/constants'
import { useToast } from '@/components/toast'
import { useLightning } from '@/components/lightning'
import { ListUsers } from '@/components/user-list'
import { Col, Row } from 'react-bootstrap'
import { proportions } from '@/lib/madness'
import { useData } from '@/components/use-data'
2024-03-23 03:47:21 +00:00
import { GrowthPieChartSkeleton } from '@/components/charts-skeletons'
import { useMemo } from 'react'
2024-04-16 22:58:26 +00:00
import { CompactLongCountdown } from '@/components/countdown'
2023-07-24 22:50:12 +00:00
const GrowthPieChart = dynamic(() => import('@/components/charts').then(mod => mod.GrowthPieChart), {
2024-03-23 03:47:21 +00:00
loading: () => <GrowthPieChartSkeleton />
2023-07-24 22:50:12 +00:00
})
2022-12-08 00:04:02 +00:00
const REWARDS_FULL = gql`
2022-12-08 00:04:02 +00:00
{
2023-08-15 17:41:51 +00:00
rewards {
2022-12-08 00:04:02 +00:00
total
time
2022-12-08 00:04:02 +00:00
sources {
name
value
}
leaderboard {
users {
id
name
photoId
2024-03-02 17:48:11 +00:00
ncomments
nposts
optional {
streak
2024-03-02 17:48:11 +00:00
stacked
spent
referrals
}
}
}
2022-12-08 00:04:02 +00:00
}
}
`
const REWARDS = gql`
{
rewards {
total
time
sources {
name
value
}
2023-07-07 19:43:53 +00:00
}
}
`
2023-07-07 19:43:53 +00:00
export const getServerSideProps = getGetServerSideProps({ query: REWARDS_FULL })
2023-07-06 17:43:51 +00:00
export function RewardLine ({ total, time }) {
2023-07-06 17:43:51 +00:00
return (
<>
<span tyle={{ whiteSpace: 'nowrap' }}>
{numWithUnits(total)} in rewards
</span>
{time &&
2024-04-16 22:58:26 +00:00
<small style={{ whiteSpace: 'nowrap' }}>
<CompactLongCountdown
className='text-monospace'
date={time}
/>
</small>}
2023-07-06 17:43:51 +00:00
</>
)
}
export default function Rewards ({ ssrData }) {
// only poll for updates to rewards and not leaderboard
2024-03-02 00:21:52 +00:00
const { data: rewardsData } = useQuery(
REWARDS,
SSR ? {} : { pollInterval: FAST_POLL_INTERVAL, nextFetchPolicy: 'cache-and-network' })
2024-03-02 00:21:52 +00:00
const { data } = useQuery(REWARDS_FULL)
const dat = useData(data, ssrData)
let { rewards: [{ total, sources, time, leaderboard }] } = useMemo(() => {
return dat || { rewards: [{}] }
}, [dat])
2022-12-08 00:04:02 +00:00
2024-03-02 00:21:52 +00:00
if (rewardsData?.rewards?.length > 0) {
2024-04-01 14:05:39 +00:00
total = rewardsData.rewards[0].total
2024-03-02 00:21:52 +00:00
sources = rewardsData.rewards[0].sources
time = rewardsData.rewards[0].time
}
2022-12-08 00:04:02 +00:00
if (!dat) return <PageLoading />
function EstimatedReward ({ rank }) {
return (
<div className='text-muted fst-italic'>
<small>
<span>estimated reward: {numWithUnits(Math.floor(total * proportions[rank - 1]))}</span>
</small>
2022-12-08 00:04:02 +00:00
</div>
)
}
return (
<Layout footerLinks>
<Link className='text-reset align-self-center' href='/items/141924'>
2024-04-01 13:53:57 +00:00
<h4 className='pt-3 text-start text-reset' style={{ lineHeight: 1.5, textDecoration: 'underline' }}>
rewards are sponsored by ... we are hiring
2024-04-01 13:53:57 +00:00
</h4>
</Link>
2024-03-31 21:53:57 +00:00
<Row className='pb-3'>
<Col lg={leaderboard?.users && 5}>
<div
className='d-flex flex-column sticky-lg-top py-5'
>
2024-03-31 21:53:57 +00:00
<h3 className='text-center text-muted'>
<div>
<RewardLine total={total} time={time} />
</div>
<Link href='/faq#how-do-i-earn-sats-on-stacker-news' className='text-info fw-normal'>
<small><small><small>learn about rewards</small></small></small>
</Link>
</h3>
<div className='my-3 w-100'>
<GrowthPieChart data={sources} />
</div>
<DonateButton />
</div>
</Col>
{leaderboard?.users &&
<Col lg={7}>
2024-03-31 21:53:57 +00:00
<h2 className='pt-5 text-center text-muted'>leaderboard</h2>
<div className='d-flex justify-content-center pt-4'>
<ListUsers users={leaderboard.users} rank Embellish={EstimatedReward} />
</div>
</Col>}
</Row>
</Layout>
2022-12-08 00:04:02 +00:00
)
}
export function DonateButton () {
const showModal = useShowModal()
const toaster = useToast()
2023-12-26 00:05:45 +00:00
const strike = useLightning()
2022-12-08 00:04:02 +00:00
const [donateToRewards] = useMutation(
gql`
mutation donateToRewards($sats: Int!, $hash: String, $hmac: String) {
donateToRewards(sats: $sats, hash: $hash, hmac: $hmac)
2022-12-08 00:04:02 +00:00
}`)
return (
<>
<Button
onClick={() => showModal(onClose => (
<Form
initial={{
amount: 10000
}}
schema={amountSchema}
Frontend payment UX cleanup (#1194) * Replace useInvoiceable with usePayment hook * Show WebLnError in QR code fallback * Fix missing removal of old zap undo code * Fix payment timeout message * Fix unused arg in super() * Also bail if invoice expired * Fix revert on reply error * Use JIT_INVOICE_TIMEOUT_MS constant * Remove unnecessary PaymentContext * Fix me as a dependency in FeeButtonContext * Fix anon sats added before act success * Optimistic updates for zaps * Fix modal not closed after custom zap * Optimistic update for custom zaps * Optimistic update for bounty payments * Consistent error handling for zaps and bounty payments * Optimistic update for poll votes * Use var balance in payment.request * Rename invoiceable to prepaid * Log cancelled invoices * Client notifications We now show notifications that are stored on the client to inform the user about following errors in the prepaid payment flow: - if a payment fails - if an invoice expires before it is paid - if a payment was interrupted (for example via page refresh) - if the action fails after payment * Remove unnecessary passing of act * Use AbortController for zap undos * Fix anon zap update not updating bolt color * Fix zap counted towards anon sats even if logged in * Fix duplicate onComplete call * Fix downzap type error * Fix "missing field 'path' while writing result" error * Pass full item in downzap props The previous commit fixed cache updates for downzaps but then the cache update for custom zaps failed because 'path' wasn't included in the server response. This commit is the proper fix. * Parse lnc rpc error messages * Add hash to InvoiceExpiredError
2024-05-28 17:18:54 +00:00
prepaid
onSubmit={async ({ amount, hash, hmac }) => {
const { error } = await donateToRewards({
variables: {
sats: Number(amount),
hash,
hmac
}
})
if (error) {
console.error(error)
toaster.danger('failed to donate')
} else {
const didStrike = strike()
if (!didStrike) {
toaster.success('donated')
}
}
onClose()
}}
>
<Input
label='amount'
name='amount'
type='number'
required
autoFocus
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
/>
<div className='d-flex'>
<SubmitButton variant='success' className='ms-auto mt-1 px-4' value='TIP'>donate</SubmitButton>
</div>
</Form>
))}
className='align-self-center'
>DONATE TO REWARDS
</Button>
2022-12-08 00:04:02 +00:00
</>
)
}