Compare commits

...

6 Commits

Author SHA1 Message Date
keyan
c83ff02a85 update awards.csv 2024-05-15 13:26:41 -05:00
keyan
85a4839538 update awards.csv absent date paid 2024-05-15 13:11:30 -05:00
Felipe Bueno
471888563e
Item popover (#1162)
* WIP Item Popover

* Hide user on ItemSumarry to avoid infinite popovers

* Introduce HoverablePopover

* Delete itempopover & userpopover css

* Fix excess bottom padding on the ItemPopover

* Fix ItemSummary: Use text for itens that doesn't have a title

* Handling #itemid/something links + Tweaks for rendering comment summary

* refine hoverable popover

---------

Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
Co-authored-by: keyan <keyan.kousha+huumn@gmail.com>
2024-05-15 12:05:50 -05:00
Tom Smith
691818e779
[1124] - Use Mempool For Fee Rate (#1171)
* Use mempool for fee rate

* Add minor logic change

* Restore carousel items
2024-05-15 10:26:49 -05:00
ekzyis
c6ab776091
Add nostr-wallet-connect-lnd container (#1174)
Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
2024-05-15 10:10:24 -05:00
ekzyis
47bfa24b57
Add lnbits container (#1173) 2024-05-15 10:09:15 -05:00
14 changed files with 354 additions and 177 deletions

View File

@ -150,4 +150,7 @@ PERSISTENCE=1
SKIP_SSL_CERT_DOWNLOAD=1
# tor
TOR_PROXY=http://127.0.0.1:7050/
TOR_PROXY=http://127.0.0.1:7050/
# lnbits
LNBITS_WEB_PORT=5000

View File

@ -1,17 +1,16 @@
import lndService from 'ln-service'
import lnd from '@/api/lnd'
const cache = new Map()
const expiresIn = 1000 * 30 // 30 seconds in milliseconds
async function fetchChainFeeRate () {
let chainFee = 0
try {
const fee = await lndService.getChainFeeRate({ lnd })
chainFee = fee.tokens_per_vbyte
} catch (err) {
console.error('fetchChainFee', err)
}
const url = 'https://mempool.space/api/v1/fees/recommended'
const chainFee = await fetch(url)
.then((res) => res.json())
.then((body) => body.hourFee)
.catch((err) => {
console.error('fetchChainFee', err)
return 0
})
cache.set('fee', { fee: chainFee, createdAt: Date.now() })
return chainFee
}

View File

@ -74,11 +74,23 @@ SatsAllDay,issue,#1137,#1125,good-first-issue,,,,2k,weareallsatoshi@getalby.com,
SatsAllDay,helpfulness,#1137,#1125,good-first-issue,,,,2k,weareallsatoshi@getalby.com,2024-05-04
itsrealfake,pr,#1138,#995,good-first-issue,,,,20k,itsrealfake2@stacker.news,2024-05-06
SouthKoreaLN,issue,#1138,#995,good-first-issue,,,,2k,south_korea_ln@stacker.news,2024-05-04
mateusdeap,helpfulness,#1138,#995,good-first-issue,,,,1k,???,???
mateusdeap,helpfulness,#1138,#995,good-first-issue,,,,1k,mateusdeap@stacker.news,???
felipebueno,pr,#1094,,,,2,,80k,felipebueno@getalby.com,2024-05-06
benalleng,helpfulness,#1127,#927,good-first-issue,,,,2k,benalleng@mutiny.plus,2024-05-04
itsrealfake,pr,#1135,#1016,good-first-issue,,,nonideal solution,10k,itsrealfake2@stacker.news,2024-05-06
SatsAllDay,issue,#1135,#1016,good-first-issue,,,,1k,weareallsatoshi@getalby.com,2024-05-04
s373nZ,issue,#1136,#1107,medium,high,,,50k,se7enz@minibits.cash,2024-05-05
benalleng,pr,#1129,#1045,good-first-issue,,,paid for advice out of band,20k,benalleng@mutiny.plus,???
benalleng,pr,#1129,#491,good-first-issue,,,,20k,benalleng@mutiny.plus,???
abhiShandy,pr,#1123,#624,good-first-issue,,,,20k,abhishandy@stacker.news,???
hkarani,pr,#1147,#1143,good-first-issue,,,,20k,asterisk32@stacker.news,???
benalleng,helpfulness,#1147,#1143,good-first-issue,,,,2k,benalleng@mutiny.plus,???
abhiShandy,pr,#1157,#1148,good-first-issue,,,,20k,abhishandy@stacker.news,???
SatsAllDay,issue,#1157,#1148,good-first-issue,,,,2k,weareallsatoshi@getalby.com,???
abhiShandy,pr,#1158,#1139,good-first-issue,,,,20k,abhishandy@stacker.news,???
SatsAllDay,issue,#1158,#1139,good-first-issue,,,,2k,weareallsatoshi@getalby.com,???
SatsAllDay,pr,#1145,#717,medium,,,,250k,weareallsatoshi@getalby.com,???
benalleng,pr,#1129,#491,good-first-issue,,,paid for advice out of band,20k,benalleng@mutiny.plus,???
benalleng,pr,#1129,#1045,easy,,2,post-humously upgraded to easy,80k,benalleng@mutiny.plus,???
SouthKoreaLN,issue,#1129,#1045,easy,,,,8k,south_korea_ln@stacker.news,???
tsmith123,pr,#1171,#1124,good-first-issue,,,bonus for refactor,40k,stickymarch60@walletofsatoshi.com,???
SatsAllDay,issue,#1171,#1124,good-first-issue,,,,4k,weareallsatoshi@getalby.com,???
felipebueno,pr,#1162,,,,2,,200k,felipebueno@getalby.com,???

1 name type pr id issue ids difficulty priority changes requested notes amount receive method date paid
74 SatsAllDay helpfulness #1137 #1125 good-first-issue 2k weareallsatoshi@getalby.com 2024-05-04
75 itsrealfake pr #1138 #995 good-first-issue 20k itsrealfake2@stacker.news 2024-05-06
76 SouthKoreaLN issue #1138 #995 good-first-issue 2k south_korea_ln@stacker.news 2024-05-04
77 mateusdeap helpfulness #1138 #995 good-first-issue 1k ??? mateusdeap@stacker.news ???
78 felipebueno pr #1094 2 80k felipebueno@getalby.com 2024-05-06
79 benalleng helpfulness #1127 #927 good-first-issue 2k benalleng@mutiny.plus 2024-05-04
80 itsrealfake pr #1135 #1016 good-first-issue nonideal solution 10k itsrealfake2@stacker.news 2024-05-06
81 SatsAllDay issue #1135 #1016 good-first-issue 1k weareallsatoshi@getalby.com 2024-05-04
82 s373nZ issue #1136 #1107 medium high 50k se7enz@minibits.cash 2024-05-05
83 benalleng abhiShandy pr #1129 #1123 #1045 #624 good-first-issue paid for advice out of band 20k benalleng@mutiny.plus abhishandy@stacker.news ???
84 benalleng hkarani pr #1129 #1147 #491 #1143 good-first-issue 20k benalleng@mutiny.plus asterisk32@stacker.news ???
85 benalleng helpfulness #1147 #1143 good-first-issue 2k benalleng@mutiny.plus ???
86 abhiShandy pr #1157 #1148 good-first-issue 20k abhishandy@stacker.news ???
87 SatsAllDay issue #1157 #1148 good-first-issue 2k weareallsatoshi@getalby.com ???
88 abhiShandy pr #1158 #1139 good-first-issue 20k abhishandy@stacker.news ???
89 SatsAllDay issue #1158 #1139 good-first-issue 2k weareallsatoshi@getalby.com ???
90 SatsAllDay pr #1145 #717 medium 250k weareallsatoshi@getalby.com ???
91 benalleng pr #1129 #491 good-first-issue paid for advice out of band 20k benalleng@mutiny.plus ???
92 benalleng pr #1129 #1045 easy 2 post-humously upgraded to easy 80k benalleng@mutiny.plus ???
93 SouthKoreaLN issue #1129 #1045 easy 8k south_korea_ln@stacker.news ???
94 tsmith123 pr #1171 #1124 good-first-issue bonus for refactor 40k stickymarch60@walletofsatoshi.com ???
95 SatsAllDay issue #1171 #1124 good-first-issue 4k weareallsatoshi@getalby.com ???
96 felipebueno pr #1162 2 200k felipebueno@getalby.com ???

View File

@ -0,0 +1,49 @@
import { useRef, useState } from 'react'
import { Popover } from 'react-bootstrap'
import OverlayTrigger from 'react-bootstrap/OverlayTrigger'
import styles from './hoverable-popover.module.css'
export default function HoverablePopover ({ id, trigger, body, onShow }) {
const [showOverlay, setShowOverlay] = useState(false)
const timeoutId = useRef(null)
const handleMouseEnter = () => {
clearTimeout(timeoutId.current)
onShow && onShow()
timeoutId.current = setTimeout(() => {
setShowOverlay(true)
}, 500)
}
const handleMouseLeave = () => {
clearTimeout(timeoutId.current)
timeoutId.current = setTimeout(() => setShowOverlay(false), 100)
}
return (
<OverlayTrigger
show={showOverlay}
placement='bottom'
onHide={handleMouseLeave}
overlay={
<Popover
onPointerEnter={handleMouseEnter}
onPointerLeave={handleMouseLeave}
className={styles.HoverablePopover}
>
<Popover.Body className={styles.HoverablePopover}>
{body}
</Popover.Body>
</Popover>
}
>
<span
onPointerEnter={handleMouseEnter}
onPointerLeave={handleMouseLeave}
>
{trigger}
</span>
</OverlayTrigger>
)
}

View File

@ -1,8 +1,8 @@
.userPopover {
.hoverablePopover {
border: 1px solid var(--theme-toolbarActive)
}
.userPopBody {
.hoverablePopBody {
font-weight: 500;
font-size: 0.9rem;
}

View File

@ -26,7 +26,7 @@ import UserPopover from './user-popover'
export default function ItemInfo ({
item, full, commentsText = 'comments',
commentTextSingular = 'comment', className, embellishUser, extraInfo, onEdit, editText,
onQuoteReply, extraBadges, nested, pinnable
onQuoteReply, extraBadges, nested, pinnable, showActionDropdown = true, showUser = true
}) {
const editThreshold = new Date(item.createdAt).getTime() + 10 * 60000
const me = useMe()
@ -61,10 +61,10 @@ export default function ItemInfo ({
{!(item.position && (pinnable || !item.subName)) && !(!item.parentId && Number(item.user?.id) === AD_USER_ID) &&
<>
<span title={`from ${numWithUnits(item.upvotes, {
abbreviate: false,
unitSingular: 'stacker',
unitPlural: 'stackers'
})} ${item.mine
abbreviate: false,
unitSingular: 'stacker',
unitPlural: 'stackers'
})} ${item.mine
? `\\ ${numWithUnits(item.meSats, { abbreviate: false })} to post`
: `(${numWithUnits(meTotalSats, { abbreviate: false })}${item.meDontLikeSats
? ` & ${numWithUnits(item.meDontLikeSats, { abbreviate: false, unitSingular: 'downsat', unitPlural: 'downsats' })}`
@ -102,12 +102,13 @@ export default function ItemInfo ({
</Link>
<span> \ </span>
<span>
<UserPopover name={item.user.name}>
<Link href={`/${item.user.name}`}>
@{item.user.name}<span> </span><Hat className='fill-grey' user={item.user} height={12} width={12} />
{embellishUser}
</Link>
</UserPopover>
{showUser &&
<UserPopover name={item.user.name}>
<Link href={`/${item.user.name}`}>
@{item.user.name}<span> </span><Hat className='fill-grey' user={item.user} height={12} width={12} />
{embellishUser}
</Link>
</UserPopover>}
<span> </span>
<Link href={`/items/${item.id}`} title={item.createdAt} className='text-reset' suppressHydrationWarning>
{timeSince(new Date(item.createdAt))}
@ -152,54 +153,57 @@ export default function ItemInfo ({
/>
</span>
</>}
<ActionDropdown>
<CopyLinkDropdownItem item={item} />
{(item.parentId || item.text) && onQuoteReply &&
<Dropdown.Item onClick={onQuoteReply}>quote reply</Dropdown.Item>}
{me && <BookmarkDropdownItem item={item} />}
{me && <SubscribeDropdownItem item={item} />}
{item.otsHash &&
<Link href={`/items/${item.id}/ots`} className='text-reset dropdown-item'>
opentimestamp
</Link>}
{item?.noteId && (
<Dropdown.Item onClick={() => window.open(`https://njump.me/${item.noteId}`, '_blank', 'noopener,noreferrer,nofollow')}>
nostr note
</Dropdown.Item>
)}
{item && item.mine && !item.noteId && !item.isJob && !item.parentId &&
<CrosspostDropdownItem item={item} />}
{me && !item.position &&
!item.mine && !item.deletedAt &&
(item.meDontLikeSats > meTotalSats
? <DropdownItemUpVote item={item} />
: <DontLikeThisDropdownItem id={item.id} />)}
{me && sub && !item.mine && !item.outlawed && Number(me.id) === Number(sub.userId) && sub.moderated &&
<>
<hr className='dropdown-divider' />
<OutlawDropdownItem item={item} />
</>}
{me && !nested && !item.mine && sub && Number(me.id) !== Number(sub.userId) &&
<>
<hr className='dropdown-divider' />
<MuteSubDropdownItem item={item} sub={sub} />
</>}
{canPin &&
<>
<hr className='dropdown-divider' />
<PinSubDropdownItem item={item} />
</>}
{item.mine && !item.position && !item.deletedAt && !item.bio &&
<>
<hr className='dropdown-divider' />
<DeleteDropdownItem itemId={item.id} type={item.title ? 'post' : 'comment'} />
</>}
{me && !item.mine &&
<>
<hr className='dropdown-divider' />
<MuteDropdownItem user={item.user} />
</>}
</ActionDropdown>
{
showActionDropdown &&
<ActionDropdown>
<CopyLinkDropdownItem item={item} />
{(item.parentId || item.text) && onQuoteReply &&
<Dropdown.Item onClick={onQuoteReply}>quote reply</Dropdown.Item>}
{me && <BookmarkDropdownItem item={item} />}
{me && <SubscribeDropdownItem item={item} />}
{item.otsHash &&
<Link href={`/items/${item.id}/ots`} className='text-reset dropdown-item'>
opentimestamp
</Link>}
{item?.noteId && (
<Dropdown.Item onClick={() => window.open(`https://njump.me/${item.noteId}`, '_blank', 'noopener,noreferrer,nofollow')}>
nostr note
</Dropdown.Item>
)}
{item && item.mine && !item.noteId && !item.isJob && !item.parentId &&
<CrosspostDropdownItem item={item} />}
{me && !item.position &&
!item.mine && !item.deletedAt &&
(item.meDontLikeSats > meTotalSats
? <DropdownItemUpVote item={item} />
: <DontLikeThisDropdownItem id={item.id} />)}
{me && sub && !item.mine && !item.outlawed && Number(me.id) === Number(sub.userId) && sub.moderated &&
<>
<hr className='dropdown-divider' />
<OutlawDropdownItem item={item} />
</>}
{me && !nested && !item.mine && sub && Number(me.id) !== Number(sub.userId) &&
<>
<hr className='dropdown-divider' />
<MuteSubDropdownItem item={item} sub={sub} />
</>}
{canPin &&
<>
<hr className='dropdown-divider' />
<PinSubDropdownItem item={item} />
</>}
{item.mine && !item.position && !item.deletedAt && !item.bio &&
<>
<hr className='dropdown-divider' />
<DeleteDropdownItem itemId={item.id} type={item.title ? 'post' : 'comment'} />
</>}
{me && !item.mine &&
<>
<hr className='dropdown-divider' />
<MuteDropdownItem user={item.user} />
</>}
</ActionDropdown>
}
{extraInfo}
</div>
)

View File

@ -0,0 +1,28 @@
import { ITEM } from '@/fragments/items'
import errorStyles from '@/styles/error.module.css'
import { useLazyQuery } from '@apollo/client'
import classNames from 'classnames'
import HoverablePopover from './hoverable-popover'
import { ItemSkeleton, ItemSummary } from './item'
export default function ItemPopover ({ id, children }) {
const [getItem, { loading, data }] = useLazyQuery(
ITEM,
{
variables: { id },
fetchPolicy: 'cache-first'
}
)
return (
<HoverablePopover
onShow={getItem}
trigger={children}
body={!data || loading
? <ItemSkeleton showUpvote={false} />
: !data.item
? <h1 className={classNames(errorStyles.status, errorStyles.describe)}>ITEM NOT FOUND</h1>
: <ItemSummary item={data.item} />}
/>
)
}

View File

@ -18,6 +18,26 @@ import { Badge } from 'react-bootstrap'
import AdIcon from '@/svgs/advertisement-fill.svg'
import { DownZap } from './dont-link-this'
import { timeLeft } from '@/lib/time'
import classNames from 'classnames'
import removeMd from 'remove-markdown'
function onItemClick (e, router, item) {
const viewedAt = commentsViewedAt(item)
if (viewedAt) {
e.preventDefault()
if (e.ctrlKey || e.metaKey) {
window.open(
`/items/${item.id}`,
'_blank',
'noopener,noreferrer'
)
} else {
router.push(
`/items/${item.id}?commentsViewedAt=${viewedAt}`,
`/items/${item.id}`)
}
}
}
export function SearchTitle ({ title }) {
return reactStringReplace(title, /\*\*\*([^*]+)\*\*\*/g, (match, i) => {
@ -51,23 +71,9 @@ export default function Item ({ item, rank, belowTitle, right, full, children, s
<div className={`${styles.main} flex-wrap`}>
<Link
href={`/items/${item.id}`}
onClick={(e) => {
const viewedAt = commentsViewedAt(item)
if (viewedAt) {
e.preventDefault()
if (e.ctrlKey || e.metaKey) {
window.open(
`/items/${item.id}`,
'_blank',
'noopener,noreferrer'
)
} else {
router.push(
`/items/${item.id}?commentsViewedAt=${viewedAt}`,
`/items/${item.id}`)
}
}
}} ref={titleRef} className={`${styles.title} text-reset me-2`}
onClick={(e) => onItemClick(e, router, item)}
ref={titleRef}
className={`${styles.title} text-reset me-2`}
>
{item.searchTitle ? <SearchTitle title={item.searchTitle} /> : item.title}
{item.pollCost && <PollIndicator item={item} />}
@ -108,7 +114,48 @@ export default function Item ({ item, rank, belowTitle, right, full, children, s
)
}
export function ItemSkeleton ({ rank, children }) {
export function ItemSummary ({ item }) {
const router = useRouter()
const link = (
<Link
href={`/items/${item.id}`}
onClick={(e) => onItemClick(e, router, item)}
className={`${item.title && styles.title} ${styles.summaryText} text-reset me-2`}
>
{item.title ?? removeMd(item.text)}
</Link>
)
const info = (
<ItemInfo
item={item}
showUser={false}
showActionDropdown={false}
extraBadges={item.title && Number(item?.user?.id) === AD_USER_ID && <Badge className={styles.newComment} bg={null}>AD</Badge>}
/>
)
return (
<div className={classNames(styles.item, 'mb-0 pb-0')}>
<div className={styles.hunk}>
{item.title
? (
<>
{link}
{info}
</>
)
: (
<>
{info}
{link}
</>
)}
</div>
</div>
)
}
export function ItemSkeleton ({ rank, children, showUpvote = true }) {
return (
<>
{rank
@ -118,7 +165,7 @@ export function ItemSkeleton ({ rank, children }) {
</div>)
: <div />}
<div className={`${styles.item} ${styles.skeleton}`}>
<UpVote className={styles.upvote} />
{showUpvote && <UpVote className={styles.upvote} />}
<div className={styles.hunk}>
<div className={`${styles.main} flex-wrap flex-md-nowrap`}>
<span className={`${styles.title} clouds text-reset flex-md-fill flex-md-shrink-0 me-2`} />
@ -149,11 +196,10 @@ function PollIndicator ({ item }) {
return (
<span className={styles.icon} title={isActive ? 'active' : 'results in'}>
<PollIcon
className={`${
isActive
? 'fill-success'
className={`${isActive
? 'fill-success'
: 'fill-grey'
} ms-1`} height={14} width={14}
} ms-1`} height={14} width={14}
/>
</span>
)

View File

@ -6,6 +6,14 @@
margin-bottom: .15rem;
}
.summaryText {
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.notification {
position: absolute;
padding: 3px;

View File

@ -43,39 +43,38 @@ export function PriceProvider ({ price, children }) {
)
}
const STORAGE_KEY = 'asSats'
const DEFAULT_SELECTION = 'fiat'
const carousel = [
'fiat',
'yep',
'1btc',
'blockHeight',
'chainFee',
'halving'
]
export default function Price ({ className }) {
const [asSats, setAsSats] = useState(undefined)
const [pos, setPos] = useState(0)
useEffect(() => {
const satSelection = window.localStorage.getItem('asSats')
setAsSats(satSelection ?? 'fiat')
const selection = window.localStorage.getItem(STORAGE_KEY) ?? DEFAULT_SELECTION
setAsSats(selection)
setPos(carousel.findIndex((item) => item === selection))
}, [])
const { price, fiatSymbol } = usePrice()
const { height: blockHeight, halving } = useBlockHeight()
const { fee: chainFee } = useChainFee()
// Options: yep, 1btc, blockHeight, undefined
// yep -> 1btc -> blockHeight -> chainFee -> undefined -> yep
const handleClick = () => {
if (asSats === 'yep') {
window.localStorage.setItem('asSats', '1btc')
setAsSats('1btc')
} else if (asSats === '1btc') {
window.localStorage.setItem('asSats', 'blockHeight')
setAsSats('blockHeight')
} else if (asSats === 'blockHeight') {
window.localStorage.setItem('asSats', 'chainFee')
setAsSats('chainFee')
} else if (asSats === 'chainFee') {
window.localStorage.setItem('asSats', 'halving')
setAsSats('halving')
} else if (asSats === 'halving') {
window.localStorage.removeItem('asSats')
setAsSats('fiat')
} else {
window.localStorage.setItem('asSats', 'yep')
setAsSats('yep')
}
const nextPos = (pos + 1) % carousel.length
window.localStorage.setItem(STORAGE_KEY, carousel[nextPos])
setAsSats(carousel[nextPos])
setPos(nextPos)
}
const compClassName = (className || '') + ' text-reset pointer'

View File

@ -22,6 +22,7 @@ import Link from 'next/link'
import { UNKNOWN_LINK_REL } from '@/lib/constants'
import isEqual from 'lodash/isEqual'
import UserPopover from './user-popover'
import ItemPopover from './item-popover'
export function SearchText ({ text }) {
return (
@ -227,7 +228,11 @@ export default memo(function Text ({ rel, imgproxyUrls, children, tab, itemId, o
try {
const linkText = parseInternalLinks(href)
if (linkText) {
return <Link href={href}>{linkText}</Link>
return (
<ItemPopover id={linkText.replace('#', '').split('/')[0]}>
<Link href={href}>{linkText}</Link>
</ItemPopover>
)
}
} catch {
// ignore errors like invalid URLs

View File

@ -1,28 +1,28 @@
import { USER } from '@/fragments/users'
import errorStyles from '@/styles/error.module.css'
import { useLazyQuery } from '@apollo/client'
import Link from 'next/link'
import { useRef, useState } from 'react'
import { Popover } from 'react-bootstrap'
import OverlayTrigger from 'react-bootstrap/OverlayTrigger'
import { UserBase, UserSkeleton } from './user-list'
import styles from './user-popover.module.css'
import classNames from 'classnames'
import Link from 'next/link'
import HoverablePopover from './hoverable-popover'
import ItemPopover from './item-popover'
import { UserBase, UserSkeleton } from './user-list'
function StackingSince ({ since }) {
return (
<small className='text-muted d-flex-inline'>
stacking since:{' '}
{since
? <Link href={`/items/${since}`}>#{since}</Link>
? (
<ItemPopover id={since}>
<Link href={`/items/${since}`}>#{since}</Link>
</ItemPopover>
)
: <span>never</span>}
</small>
)
}
export default function UserPopover ({ name, children }) {
const [showOverlay, setShowOverlay] = useState(false)
const [getUser, { loading, data }] = useLazyQuery(
USER,
{
@ -31,52 +31,19 @@ export default function UserPopover ({ name, children }) {
}
)
const timeoutId = useRef(null)
const handleMouseEnter = () => {
clearTimeout(timeoutId.current)
getUser()
timeoutId.current = setTimeout(() => {
setShowOverlay(true)
}, 500)
}
const handleMouseLeave = () => {
clearTimeout(timeoutId.current)
timeoutId.current = setTimeout(() => setShowOverlay(false), 100)
}
return (
<OverlayTrigger
show={showOverlay}
placement='bottom'
onHide={handleMouseLeave}
overlay={
<Popover
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
className={styles.userPopover}
>
<Popover.Body className={styles.userPopBody}>
{!data || loading
? <UserSkeleton />
: !data.user
? <h1 className={classNames(errorStyles.status, errorStyles.describe)}>USER NOT FOUND</h1>
: (
<UserBase user={data.user} className='mb-0 pb-0'>
<StackingSince since={data.user.since} />
</UserBase>
)}
</Popover.Body>
</Popover>
}
>
<span
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
{children}
</span>
</OverlayTrigger>
<HoverablePopover
onShow={getUser}
trigger={children}
body={!data || loading
? <UserSkeleton />
: !data.user
? <h1 className={classNames(errorStyles.status, errorStyles.describe)}>USER NOT FOUND</h1>
: (
<UserBase user={data.user} className='mb-0 pb-0'>
<StackingSince since={data.user.since} />
</UserBase>
)}
/>
)
}

View File

@ -471,6 +471,51 @@ services:
- app
labels:
CONNECT: "localhost:8025"
nwc:
build:
context: ./docker/nwc
container_name: nwc
profiles:
- payments
restart: unless-stopped
depends_on:
stacker_lnd:
condition: service_healthy
restart: true
volumes:
- ./docker/lnd/stacker:/root/.lnd
environment:
- RUST_LOG=info
entrypoint:
- 'nostr-wallet-connect-lnd'
- '--relay'
- 'wss://relay.damus.io'
- '--macaroon-file'
- '/root/.lnd/regtest/admin.macaroon'
- '--cert-file'
- '/root/.lnd/tls.cert'
- '--lnd-host'
- 'stacker_lnd'
- '--lnd-port'
- '10009'
lnbits:
image: lnbits/lnbits:0.12.5
container_name: lnbits
profiles:
- payments
restart: unless-stopped
ports:
- "${LNBITS_WEB_PORT}:5000"
depends_on:
- stacker_lnd
environment:
- LNBITS_BACKEND_WALLET_CLASS=LndWallet
- LND_GRPC_ENDPOINT=stacker_lnd
- LND_GRPC_PORT=10009
- LND_GRPC_CERT=/app/.lnd/tls.cert
- LND_GRPC_MACAROON=/app/.lnd/regtest/admin.macaroon
volumes:
- ./docker/lnd/stacker:/app/.lnd
volumes:
db:
os:

12
docker/nwc/Dockerfile Normal file
View File

@ -0,0 +1,12 @@
FROM rust:1.78
RUN wget https://github.com/benthecarman/nostr-wallet-connect-lnd/archive/9d53490f0a0cf655030e4ef4d32b478d7f29af5b.zip \
&& unzip 9d53490f0a0cf655030e4ef4d32b478d7f29af5b.zip
WORKDIR nostr-wallet-connect-lnd-9d53490f0a0cf655030e4ef4d32b478d7f29af5b
RUN apt-get update -y \
&& apt-get install -y cmake \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
RUN cargo build --release && cargo install --path .