Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable Share on Polkaverse for Reward Modal #226

Merged
merged 15 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/feature-based.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ jobs:
GH_SELLER_CLIENT_ID=5DYm3Wk4aa1BbfhH1ajmY6MNEELXoicmKRnP4tzHYjSKnD9K
GH_SELLER_TOKEN_SIGNER=retire strong pole intact cool music high path salt praise stadium spatial
GH_SERVER_MNEMONIC=plunge pumpkin penalty segment cattle more print below fat lemon clap uniform
GH_NEXT_PUBLIC_DATAHUB_QUERY_URL=https://leaderboards-data-hub.subsocial.network/graphql
GH_NEXT_PUBLIC_DATAHUB_SUBSCRIPTION_URL=wss://leaderboards-data-hub.subsocial.network/graphql-ws
GH_DATAHUB_QUEUE_URL=https://leaderboards-queue-data-hub.subsocial.network/graphql
GH_NEXT_PUBLIC_DATAHUB_QUERY_URL=https://sub-data-hub.subsocial.network/graphql
GH_NEXT_PUBLIC_DATAHUB_SUBSCRIPTION_URL=wss://sub-data-hub.subsocial.network/graphql-ws
GH_DATAHUB_QUEUE_URL=https://sub-queue-data-hub.subsocial.network/graphql
GH_DATAHUB_QUEUE_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6dHJ1ZX0.jpXwkIJ4DpV4IvSI3eWVVXE6x89qr_GIq7IlbBv5YE0
tags: |
${{ env.image }}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
"graphql-request": "^5.2.0",
"graphql-tag": "^2.11.0",
"graphql-ws": "^5.14.3",
"html2canvas": "^1.4.1",
"isbot": "^3.0.14",
"isomorphic-ws": "^5.0.0",
"jdenticon": "^3.2.0",
Expand Down
9 changes: 8 additions & 1 deletion src/components/leaderboard/ProgressModal.module.sass
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
@import 'src/styles/subsocial-vars.scss'

.ProgressModal
color: white

&:global(#progress-image)
:global(.ant-modal-content)
border-radius: 0
box-shadow: none

:global(.ant-modal-content)
overflow: hidden
color: white

.DiamondIcon
font-size: 130px
Expand Down Expand Up @@ -47,6 +53,7 @@
position: relative

.Diamond
overflow: visible !important
width: 44px
height: 44px
position: absolute
Expand Down
240 changes: 196 additions & 44 deletions src/components/leaderboard/ProgressModal.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
import { Button, Tooltip } from 'antd'
import clsx from 'clsx'
import dayjs from 'dayjs'
import html2canvas from 'html2canvas'
import { useEffect, useState } from 'react'
import { SlQuestion } from 'react-icons/sl'
import { ReactNode } from 'react-markdown'
import CustomModal from 'src/components/utils/CustomModal'
import { useSelectProfile } from 'src/rtk/app/hooks'
import { resolveIpfsUrl } from 'src/ipfs'
import { useFetchProfileSpace, useSelectProfile } from 'src/rtk/app/hooks'
import { useFetchUserPrevReward } from 'src/rtk/features/activeStaking/hooks'
import { PrevRewardStatus } from 'src/rtk/features/activeStaking/prevRewardSlice'
import { resizeImage } from 'src/utils/image'
import { useMyAddress } from '../auth/MyAccountsContext'
import { FormatBalance } from '../common/balances'
import { useDefaultSpaceIdToPost } from '../posts/editor/ModalEditor'
import Avatar from '../profiles/address-views/Avatar'
import { useSubsocialApi } from '../substrate'
import { twitterShareUrl } from '../urls'
import { openNewWindow } from '../urls/helpers'
import { fullUrl, openNewWindow } from '../urls/helpers'
import { DfImage } from '../utils/DfImage'
import DiamondIcon from '../utils/icons/Diamond'
import { controlledMessage } from '../utils/Message'
import styles from './ProgressModal.module.sass'

const progressModalStorage = {
Expand Down Expand Up @@ -51,45 +57,112 @@ const statusClassName: Record<DisplayedStatus, string> = {

const contentMap: Record<
'lastWeek' | 'yesterday',
Record<DisplayedStatus, { title: string; subtitle: string }>
Record<
DisplayedStatus,
{ title: string; subtitle: (withFirstPersonPerspective?: boolean) => string }
>
> = {
lastWeek: {
full: {
title: 'Week Striker',
subtitle: 'You dominated last week, and maximized your rewards every day! Impressive!',
subtitle: firstPerson =>
`${firstPerson ? 'I' : 'You'} dominated last week, and maximized ${
firstPerson ? 'my' : 'your'
} rewards every day! Impressive!`,
},
half: {
title: 'Halfway Hero',
subtitle:
"You had some good activity last week, but you still gave up some rewards. Let's see just a little more this week!",
subtitle: firstPerson =>
`${firstPerson ? 'I' : 'You'} had some good activity last week, but ${
firstPerson ? 'I' : 'you'
} still gave up some rewards. Let's see just a little more this week!`,
},
},
yesterday: {
full: {
title: 'Hustler',
subtitle:
'Incredible work yesterday! You completed every task and went above and beyond. Your energy is unmatched!',
subtitle: firstPerson =>
`Incredible work yesterday! ${
firstPerson ? 'I' : 'You'
} completed every task and went above and beyond. ${
firstPerson ? 'My' : 'Your'
} energy is unmatched!`,
},
half: {
title: 'Cherry Picker',
subtitle:
'Good effort yesterday, but you missed out on maximum rewards. Make sure to like at least 10 posts today!',
subtitle: firstPerson =>
`Good effort yesterday, but ${
firstPerson ? 'I' : 'You'
} missed out on maximum rewards. Make sure to like at least 10 posts today!`,
},
},
}

function InnerProgressModal() {
const myAddress = useMyAddress() ?? ''
const [visible, setVisible] = useState(false)
const profile = useSelectProfile(myAddress)
const { data } = useFetchUserPrevReward(myAddress)
const { loading: loadingProfile } = useFetchProfileSpace({ id: myAddress })

useEffect(() => {
if (!data) return
if (!data || loadingProfile) return
if (data.rewardStatus === 'none') return

setVisible(true)
}, [data])
}, [data, loadingProfile])

const isUsingLastWeekData = data?.period === 'WEEK'
const status = data?.rewardStatus ?? 'half'

if (status === 'none') {
return null
}

return (
<>
<CustomModal
visible={visible}
onCancel={() => {
setVisible(false)
progressModalStorage.close()
}}
title={`Your progress ${isUsingLastWeekData ? 'last week' : 'yesterday'}`}
closable
className={clsx(styles.ProgressModal, statusClassName[status])}
contentClassName={styles.Content}
>
<ProgressPanel withButtons />
<div
id='progress-image'
className={clsx(styles.ProgressModal, statusClassName[status])}
style={{ width: '400px', display: 'none' }}
>
<div className='ant-modal-content p-3 pb-4'>
<ProgressPanel withFirstPersonPerspective />
</div>
</div>
</CustomModal>
</>
)
}

function ProgressPanel({
withButtons,
withFirstPersonPerspective,
}: {
withButtons?: boolean
withFirstPersonPerspective?: boolean
}) {
const { ipfs } = useSubsocialApi()

const myAddress = useMyAddress() ?? ''
const profile = useSelectProfile(myAddress)

const { data } = useFetchUserPrevReward(myAddress)

const { defaultSpaceIdToPost } = useDefaultSpaceIdToPost()

const [loading, setLoading] = useState(false)

const isUsingLastWeekData = data?.period === 'WEEK'
const status = data?.rewardStatus ?? 'half'
Expand All @@ -105,20 +178,82 @@ function InnerProgressModal() {
hasMissedReward = BigInt(data?.missedReward || '0') > 0
} catch {}

const spaceHandleOrId = profile?.struct.handle || profile?.id
const handle = profile?.struct.handle
const spaceHandleOrId = (handle && `@${handle}`) || profile?.id

const generateImage = async (onSuccess: (image: string) => void) => {
const element = document.getElementById('progress-image')
if (!element) return

setLoading(true)
const image = await html2canvas(element, {
backgroundColor: status === 'full' ? '#7534A9' : '#F57F00',
allowTaint: true,
useCORS: true,
scale: 2,
onclone: doc => {
doc.getElementById('progress-image')!.style.display = 'block'
},
})

image.toBlob(async blob => {
if (!blob) {
setLoading(false)
return
}

const compressedImage = await resizeImage(blob)
let cid: string | undefined
try {
cid = await ipfs.saveFileToOffchain(compressedImage)
if (!cid) throw new Error('Failed to save image to IPFS')

return (
<CustomModal
visible={visible}
onCancel={() => {
setVisible(false)
progressModalStorage.close()
}}
title={`Your progress ${isUsingLastWeekData ? 'last week' : 'yesterday'}`}
closable
className={clsx(styles.ProgressModal, statusClassName[status])}
contentClassName={styles.Content}
>
onSuccess(resolveIpfsUrl(cid))
} catch (err: any) {
controlledMessage({
message: 'Failed to generate reward image',
description: err.message || undefined,
type: 'error',
})
console.error(err)
}
setLoading(false)
})
}

const shareOnPolkaverse = () => {
const { isZero, value } = formatSUB(data?.earned)
const title = `I earned ${isZero ? '' : `${value} `}SUB ${
isUsingLastWeekData ? 'last week for my activity' : 'yesterday'
} on Subsocial!`
const desc = 'Being a part of The Creator Economy is great!'
generateImage(image => {
window.open(
fullUrl(
`${defaultSpaceIdToPost}/posts/new?image=${encodeURIComponent(
image,
)}&title=${encodeURIComponent(title)}&body=${encodeURIComponent(desc)}`,
),
'_blank',
)
})
}

const shareOnX = () => {
const { isZero, value } = formatSUB(data?.earned)
openNewWindow(
twitterShareUrl(
spaceHandleOrId ? `/${spaceHandleOrId}` : `/accounts/${myAddress}`,
`I earned ${isZero ? '' : `${value} `}#SUB ${
isUsingLastWeekData ? 'last week for my activity' : 'yesterday'
} on @SubsocialChain! 🥳\n\nFollow me here and join The Creator Economy!`,
),
)
}

return (
<>
<DiamondIcon className={styles.DiamondIcon} />
<div className={clsx(styles.ProgressModalContent, 'mt-2')}>
<div className='d-flex flex-column align-items-center'>
Expand All @@ -132,7 +267,9 @@ function InnerProgressModal() {
/>
</div>
<span className='text-center FontLarge FontWeightBold mb-2'>{usedContent.title}</span>
<p className='text-center ColorSlate mb-0'>{usedContent.subtitle}</p>
<p className='text-center ColorSlate mb-0'>
{usedContent.subtitle(withFirstPersonPerspective)}
</p>
</div>
<div className='d-flex w-100 GapSmall'>
<RewardCard
Expand Down Expand Up @@ -167,24 +304,28 @@ function InnerProgressModal() {
)}
</div>
</div>
<Button
type='default'
size='large'
className='mt-4'
onClick={() =>
openNewWindow(
twitterShareUrl(
spaceHandleOrId ? `/${spaceHandleOrId}` : `/accounts/${myAddress}`,
`I earned #SUB ${
isUsingLastWeekData ? 'last week for my activity' : 'yesterday'
} on @SubsocialChain! 🥳\n\nFollow me here and join The Creator Economy!`,
),
)
}
>
Share on X!
</Button>
</CustomModal>
{withButtons && (
<div
className='GapNormal mt-4'
style={{ display: 'grid', gridTemplateColumns: defaultSpaceIdToPost ? '1fr 1fr' : '1fr' }}
>
<Button type='default' size='large' loading={loading} onClick={() => shareOnX()}>
Share on X
</Button>
{defaultSpaceIdToPost && (
<Button
type='default'
ghost
size='large'
onClick={() => shareOnPolkaverse()}
loading={loading}
>
Share on Polkaverse
</Button>
)}
</div>
)}
</>
)
}

Expand Down Expand Up @@ -223,3 +364,14 @@ function RewardCard({
</div>
)
}

function formatSUB(value: string | undefined) {
try {
if (!value) throw new Error('Value is not defined')

const parsedValue = BigInt(value) / BigInt(10 ** 10)
return { value: parsedValue.toString(), isZero: parsedValue <= 0 }
} catch {
return { value: '0', isZero: true }
}
}
Loading
Loading