add ln addr + lnurl pay qr code to profile pages

This commit is contained in:
keyan 2022-05-06 14:32:20 -05:00
parent 4ba1227605
commit aa4ac2ecc9
8 changed files with 65 additions and 48 deletions

View File

@ -868,7 +868,7 @@ function newTimedOrderByWeightedSats (num) {
return `
GROUP BY "Item".id
ORDER BY (SUM(CASE WHEN "ItemAct".act = 'VOTE' AND "Item"."userId" <> "ItemAct"."userId" THEN users.trust ELSE 0 END)/POWER(EXTRACT(EPOCH FROM ($${num} - "Item".created_at))/3600+2, 1.3) +
GREATEST(SUM(CASE WHEN "ItemAct".act = 'BOOST' THEN "ItemAct".sats ELSE 0 END)-1000+5, 0)/POWER(EXTRACT(EPOCH FROM ($${num} - "Item".created_at))/3600+2, 5)) DESC NULLS LAST, "Item".id DESC`
GREATEST(SUM(CASE WHEN "ItemAct".act = 'BOOST' THEN "ItemAct".sats ELSE 0 END)-1000+5, 0)/POWER(EXTRACT(EPOCH FROM ($${num} - "Item".created_at))/3600+2, 3)) DESC NULLS LAST, "Item".id DESC`
}
const TOP_ORDER_BY_SATS = 'GROUP BY "Item".id ORDER BY (SUM(CASE WHEN "ItemAct".act = \'VOTE\' AND "Item"."userId" <> "ItemAct"."userId" THEN users.trust ELSE 0 END)) DESC NULLS LAST, "Item".created_at DESC'

View File

@ -32,11 +32,7 @@ export default {
return null
}
models.user.update(
{ where: { id: me.id }, data: { lastSeenAt: new Date() } }
).catch(console.log)
return await models.user.findUnique({ where: { id: me.id } })
return await models.user.update({ where: { id: me.id }, data: { lastSeenAt: new Date() } })
},
user: async (parent, { name }, { models }) => {
return await models.user.findUnique({ where: { name } })
@ -48,7 +44,9 @@ export default {
throw new AuthenticationError('you must be logged in')
}
return me.name?.toUpperCase() === name?.toUpperCase() || !(await models.user.findUnique({ where: { name } }))
const user = await models.user.findUnique({ where: { id: me.id } })
return user.name?.toUpperCase() === name?.toUpperCase() || !(await models.user.findUnique({ where: { name } }))
},
topUsers: async (parent, { cursor, within, userType }, { models, me }) => {
const decodedCursor = decodeCursor(cursor)
@ -136,7 +134,7 @@ export default {
} else {
([item] = await serialize(models,
models.$queryRaw(`${SELECT} FROM create_bio($1, $2, $3) AS "Item"`,
`@${me.name}'s bio`, bio, Number(me.id))))
`@${user.name}'s bio`, bio, Number(me.id))))
}
await createMentions(item, models)

View File

@ -178,9 +178,11 @@ export default {
throw new UserInputError('amount must be positive', { argumentName: 'amount' })
}
const user = await models.user.findUnique({ where: { id: me.id } })
// set expires at to 3 hours into future
const expiresAt = new Date(new Date().setHours(new Date().getHours() + 3))
const description = `${amount} sats for @${me.name} on stacker.news`
const description = `${amount} sats for @${user.name} on stacker.news`
try {
const invoice = await createInvoice({
description,
@ -271,10 +273,12 @@ async function createWithdrawal (parent, { invoice, maxFee }, { me, models, lnd
const msatsFee = Number(maxFee) * 1000
const user = await models.user.findUnique({ where: { id: me.id } })
// create withdrawl transactionally (id, bolt11, amount, fee)
const [withdrawl] = await serialize(models,
models.$queryRaw`SELECT * FROM create_withdrawl(${decoded.id}, ${invoice},
${Number(decoded.mtokens)}, ${msatsFee}, ${me.name})`)
${Number(decoded.mtokens)}, ${msatsFee}, ${user.name})`)
payViaPaymentRequest({
lnd,

View File

@ -0,0 +1,21 @@
import { useState } from 'react'
import { Modal } from 'react-bootstrap'
export default function ModalButton ({ children, clicker }) {
const [show, setShow] = useState()
return (
<>
<Modal
show={show}
onHide={() => setShow(false)}
>
<div className='modal-close' onClick={() => setShow(false)}>X</div>
<Modal.Body>
{children}
</Modal.Body>
</Modal>
<div className='pointer' onClick={() => setShow(true)}>{clicker}</div>
</>
)
}

View File

@ -10,9 +10,11 @@ import { gql, useApolloClient, useMutation } from '@apollo/client'
import styles from './user-header.module.css'
import { useMe } from './me'
import { NAME_MUTATION, NAME_QUERY } from '../fragments/users'
import Image from 'next/image'
// import Image from 'next/image'
import QRCode from 'qrcode.react'
import LightningIcon from '../svgs/bolt.svg'
import ModalButton from './modal-button'
import { encodeLNUrl } from '../lib/lnurl'
export default function UserHeader ({ user }) {
const [editting, setEditting] = useState(false)
@ -22,7 +24,7 @@ export default function UserHeader ({ user }) {
const [setName] = useMutation(NAME_MUTATION)
const isMe = me?.name === user.name
const Satistics = () => <div className={`mb-4 ${styles.username} text-success`}>{isMe ? `${user.sats} sats \\ ` : ''}{user.stacked} stacked</div>
const Satistics = () => <div className={`mb-2 ${styles.username} text-success`}>{isMe ? `${user.sats} sats \\ ` : ''}{user.stacked} stacked</div>
const UserSchema = Yup.object({
name: Yup.string()
@ -40,14 +42,16 @@ export default function UserHeader ({ user }) {
})
})
const lnurlp = encodeLNUrl(new URL(`https://stacker.news/.well-known/lnurlp/${user.name}`))
return (
<>
<div className='d-flex align-items-center mt-2 flex-wrap'>
<Image
src='/dorian400.jpg' width='200' height='166' layout='fixed'
{/* <Image
src='/dorian400.jpg' width='135' height='135' layout='fixed'
className={styles.userimg}
/>
<div className='ml-3'>
/> */}
<div>
{editting
? (
<Form
@ -85,7 +89,7 @@ export default function UserHeader ({ user }) {
setEditting(false)
}}
>
<div className='d-flex align-items-center mb-1'>
<div className='d-flex align-items-center mb-2'>
<Input
prepend=<InputGroup.Text>@</InputGroup.Text>
name='name'
@ -98,22 +102,30 @@ export default function UserHeader ({ user }) {
</Form>
)
: (
<div className='d-flex align-items-center mb-1'>
<div className='d-flex align-items-center mb-2'>
<div className={styles.username}>@{user.name}</div>
{isMe &&
<Button className='py-0' variant='link' onClick={() => setEditting(true)}>edit nym</Button>}
</div>
)}
<Satistics user={user} />
<Button className='font-weight-bold'>
<LightningIcon
width={20}
height={20}
className='mr-1'
/>{user.name}@stacker.news
</Button>
<ModalButton
clicker={
<Button className='font-weight-bold'>
<LightningIcon
width={20}
height={20}
className='mr-1'
/>{user.name}@stacker.news
</Button>
}
>
<a className='d-flex m-auto p-3' style={{ background: 'white', width: 'fit-content' }} href={`lightning:${lnurlp}`}>
<QRCode className='d-flex m-auto' value={lnurlp} renderAs='svg' size={300} />
</a>
<div className='text-center font-weight-bold text-muted mt-3'>click or scan</div>
</ModalButton>
</div>
<QRCode className='ml-auto' value='fdsajfkldsajlkfjdlksajfkldjsalkjfdklsa' renderAs='svg' size={166} />
</div>
<Nav
className={styles.nav}

View File

@ -1,19 +1,9 @@
import { randomBytes, createHash } from 'crypto'
import { createHash } from 'crypto'
import { bech32 } from 'bech32'
export function lnurlAuth (params) {
// generate secret (32 random bytes)
const secret = Buffer.from(randomBytes(32), 'hex')
// create url
const url = new URL(process.env.LNAUTH_URL)
url.searchParams = new URLSearchParams({
...params,
k1: secret
})
// bech32 encode url
export function encodeLNUrl (url) {
const words = bech32.toWords(Buffer.from(url.toString(), 'utf8'))
const encodedUrl = bech32.encode('lnurl', words, 1023)
return { secret, encodedUrl }
return bech32.encode('lnurl', words, 1023)
}
export function lnurlPayMetadataString (username) {

View File

@ -22,13 +22,6 @@ const options = {
token.id = user.id
}
// XXX We need to update the user name incase they update it ... kind of hacky
// better if we use user id everywhere an ignore the username ...
if (token?.id) {
const { name } = await prisma.user.findUnique({ where: { id: token.id } })
token.name = name
}
// sign them up for the newsletter
if (isNewUser && profile.email) {
fetch(process.env.LIST_MONK_URL + '/api/subscribers', {
@ -52,7 +45,6 @@ const options = {
async session (session, token) {
// we need to add additional session params here
session.user.id = token.id
session.user.name = token.name
return session
}
},

View File

@ -38,7 +38,7 @@ const apolloServer = new ApolloServer({
models,
lnd,
me: session
? await models.user.findUnique({ where: { id: session.user?.id } })
? session.user
: null,
search
}