feat: list recent supporters
This commit is contained in:
46
src/components/Donation/RecentSupporters.tsx
Normal file
46
src/components/Donation/RecentSupporters.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { formatAmount } from '@/lib/lightning'
|
||||
import lightning, { TRecentSupporter } from '@/services/lightning.service'
|
||||
import { useEffect, useState } from 'react'
|
||||
import UserAvatar from '../UserAvatar'
|
||||
import Username from '../Username'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function RecentSupporters() {
|
||||
const { t } = useTranslation()
|
||||
const [supporters, setSupporters] = useState<TRecentSupporter[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
const init = async () => {
|
||||
const items = await lightning.fetchRecentSupporters()
|
||||
setSupporters(items)
|
||||
}
|
||||
init()
|
||||
}, [])
|
||||
|
||||
if (!supporters.length) return null
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="font-semibold text-center">{t('Recent Supporters')}</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
{supporters.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex items-center justify-between rounded-md border p-2 sm:p-4 gap-2"
|
||||
>
|
||||
<div className="flex items-center gap-2 flex-1 w-0">
|
||||
<UserAvatar userId={item.pubkey} />
|
||||
<div className="flex-1 w-0">
|
||||
<Username className="font-semibold w-fit" userId={item.pubkey} />
|
||||
<div className="text-xs text-muted-foreground line-clamp-3">{item.comment}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="font-semibold text-yellow-400 shrink-0">
|
||||
{formatAmount(item.amount)} {t('sats')}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { cn } from '@/lib/utils'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ZapDialog from '../ZapDialog'
|
||||
import RecentSupporters from './RecentSupporters'
|
||||
|
||||
export default function Donation({ className }: { className?: string }) {
|
||||
const { t } = useTranslation()
|
||||
@@ -38,6 +39,7 @@ export default function Donation({ className }: { className?: string }) {
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<RecentSupporters />
|
||||
<ZapDialog
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
|
||||
@@ -204,6 +204,7 @@ export default {
|
||||
'Temporarily display this note': 'عرض هذه الملاحظة مؤقتاً',
|
||||
buttonFollowing: 'جارٍ المتابعة',
|
||||
'Are you sure you want to unfollow this user?':
|
||||
'هل أنت متأكد أنك تريد إلغاء متابعة هذا المستخدم؟'
|
||||
'هل أنت متأكد أنك تريد إلغاء متابعة هذا المستخدم؟',
|
||||
'Recent Supporters': 'الداعمين الجدد'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,6 +209,7 @@ export default {
|
||||
'Temporarily display this note': 'Notiz vorübergehend anzeigen',
|
||||
buttonFollowing: 'Folge',
|
||||
'Are you sure you want to unfollow this user?':
|
||||
'Möchtest du diesem Benutzer wirklich nicht mehr folgen?'
|
||||
'Möchtest du diesem Benutzer wirklich nicht mehr folgen?',
|
||||
'Recent Supporters': 'Neueste Unterstützer'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,6 +204,7 @@ export default {
|
||||
'Earlier notifications': 'Earlier notifications',
|
||||
'Temporarily display this note': 'Temporarily display this note',
|
||||
buttonFollowing: 'Following',
|
||||
'Are you sure you want to unfollow this user?': 'Are you sure you want to unfollow this user?'
|
||||
'Are you sure you want to unfollow this user?': 'Are you sure you want to unfollow this user?',
|
||||
'Recent Supporters': 'Recent Supporters'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,6 +209,7 @@ export default {
|
||||
'Temporarily display this note': 'Mostrar esta nota temporalmente',
|
||||
buttonFollowing: 'Siguiendo',
|
||||
'Are you sure you want to unfollow this user?':
|
||||
'¿Estás seguro de que deseas dejar de seguir a este usuario?'
|
||||
'¿Estás seguro de que deseas dejar de seguir a este usuario?',
|
||||
'Recent Supporters': 'Últimos patrocinadores'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,6 +207,7 @@ export default {
|
||||
'Temporarily display this note': 'Afficher temporairement cette note',
|
||||
buttonFollowing: 'Suivi',
|
||||
'Are you sure you want to unfollow this user?':
|
||||
'Êtes-vous sûr de vouloir arrêter de suivre cet utilisateur ?'
|
||||
'Êtes-vous sûr de vouloir arrêter de suivre cet utilisateur ?',
|
||||
'Recent Supporters': 'Derniers soutiens'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +205,7 @@ export default {
|
||||
'Earlier notifications': '以前の通知',
|
||||
'Temporarily display this note': 'このノートを一時的に表示',
|
||||
buttonFollowing: 'フォロー中',
|
||||
'Are you sure you want to unfollow this user?': 'このユーザーのフォローを解除しますか?'
|
||||
'Are you sure you want to unfollow this user?': 'このユーザーのフォローを解除しますか?',
|
||||
'Recent Supporters': '最近のサポーター'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,6 +207,7 @@ export default {
|
||||
'Temporarily display this note': 'Tymczas wyświetl ten wpis',
|
||||
buttonFollowing: 'Obserwujesz',
|
||||
'Are you sure you want to unfollow this user?':
|
||||
'Czy na pewno chcesz przestać obserwować tego użytkownika?'
|
||||
'Czy na pewno chcesz przestać obserwować tego użytkownika?',
|
||||
'Recent Supporters': 'Ostatni wspierający'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
export default {
|
||||
translation: {
|
||||
// NOTE: The translations below were generated by ChatGPT and have not yet been verified.
|
||||
'Welcome! 🥳': 'Bem-vindo! 🥳',
|
||||
About: 'Sobre',
|
||||
'New Note': 'Nova nota',
|
||||
@@ -207,6 +206,9 @@ export default {
|
||||
'Temporarily display this note': 'Exibir esta nota temporariamente',
|
||||
buttonFollowing: 'Seguindo',
|
||||
'Are you sure you want to unfollow this user?':
|
||||
'Tem certeza de que deseja deixar de seguir este usuário?'
|
||||
'Tem certeza de que deseja deixar de seguir este usuário?',
|
||||
|
||||
// NOTE: The translations below were generated by ChatGPT and have not yet been verified.
|
||||
'Recent Supporters': 'Apoiadores recentes'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,6 +207,7 @@ export default {
|
||||
'Temporarily display this note': 'Exibir esta nota temporariamente',
|
||||
buttonFollowing: 'Seguindo',
|
||||
'Are you sure you want to unfollow this user?':
|
||||
'Tem certeza de que deseja deixar de seguir este usuário?'
|
||||
'Tem certeza de que deseja deixar de seguir este usuário?',
|
||||
'Recent Supporters': 'Apoiadores Recentes'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,6 +209,7 @@ export default {
|
||||
'Temporarily display this note': 'Временно отобразить эту заметку',
|
||||
buttonFollowing: 'Подписан',
|
||||
'Are you sure you want to unfollow this user?':
|
||||
'Вы уверены, что хотите отписаться от этого пользователя?'
|
||||
'Вы уверены, что хотите отписаться от этого пользователя?',
|
||||
'Recent Supporters': 'Недавние спонсоры'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +205,7 @@ export default {
|
||||
'Earlier notifications': '更早的通知',
|
||||
'Temporarily display this note': '临时显示此笔记',
|
||||
buttonFollowing: '已关注',
|
||||
'Are you sure you want to unfollow this user?': '确定要取消关注此用户吗?'
|
||||
'Are you sure you want to unfollow this user?': '确定要取消关注此用户吗?',
|
||||
'Recent Supporters': '最近的支持者'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { BIG_RELAY_URLS } from '@/constants'
|
||||
import { BIG_RELAY_URLS, CODY_PUBKEY } from '@/constants'
|
||||
import { extractZapInfoFromReceipt } from '@/lib/event'
|
||||
import { TProfile } from '@/types'
|
||||
import {
|
||||
@@ -17,9 +17,12 @@ import { makeZapRequest } from 'nostr-tools/nip57'
|
||||
import { utf8Decoder } from 'nostr-tools/utils'
|
||||
import client from './client.service'
|
||||
|
||||
export type TRecentSupporter = { pubkey: string; amount: number; comment?: string }
|
||||
|
||||
class LightningService {
|
||||
static instance: LightningService
|
||||
private provider: WebLNProvider | null = null
|
||||
private recentSupportersCache: TRecentSupporter[] | null = null
|
||||
|
||||
constructor() {
|
||||
if (!LightningService.instance) {
|
||||
@@ -150,6 +153,36 @@ class LightningService {
|
||||
})
|
||||
}
|
||||
|
||||
async fetchRecentSupporters() {
|
||||
if (this.recentSupportersCache) {
|
||||
return this.recentSupportersCache
|
||||
}
|
||||
const relayList = await client.fetchRelayList(CODY_PUBKEY)
|
||||
const events = await client.fetchEvents(relayList.read.slice(0, 4), {
|
||||
authors: ['79f00d3f5a19ec806189fcab03c1be4ff81d18ee4f653c88fac41fe03570f432'], // alby
|
||||
kinds: [kinds.Zap],
|
||||
'#p': [CODY_PUBKEY],
|
||||
since: dayjs().subtract(1, 'month').unix()
|
||||
})
|
||||
events.sort((a, b) => b.created_at - a.created_at)
|
||||
const map = new Map<string, { pubkey: string; amount: number; comment?: string }>()
|
||||
events.forEach((event) => {
|
||||
const info = extractZapInfoFromReceipt(event)
|
||||
if (!info || info.eventId || !info.senderPubkey || info.senderPubkey === CODY_PUBKEY) return
|
||||
|
||||
const { amount, comment, senderPubkey } = info
|
||||
const item = map.get(senderPubkey)
|
||||
if (!item) {
|
||||
map.set(senderPubkey, { pubkey: senderPubkey, amount, comment })
|
||||
} else {
|
||||
item.amount += amount
|
||||
if (!item.comment && comment) item.comment = comment
|
||||
}
|
||||
})
|
||||
this.recentSupportersCache = Array.from(map.values()).sort((a, b) => b.amount - a.amount)
|
||||
return this.recentSupportersCache
|
||||
}
|
||||
|
||||
private async getZapEndpoint(profile: TProfile): Promise<null | {
|
||||
callback: string
|
||||
lnurl: string
|
||||
|
||||
Reference in New Issue
Block a user