add ln addr + lnurl pay qr code to profile pages
This commit is contained in:
		
							parent
							
								
									4ba1227605
								
							
						
					
					
						commit
						aa4ac2ecc9
					
				@ -868,7 +868,7 @@ function newTimedOrderByWeightedSats (num) {
 | 
				
			|||||||
  return `
 | 
					  return `
 | 
				
			||||||
    GROUP BY "Item".id
 | 
					    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) +
 | 
					    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'
 | 
					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'
 | 
				
			||||||
 | 
				
			|||||||
@ -32,11 +32,7 @@ export default {
 | 
				
			|||||||
        return null
 | 
					        return null
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      models.user.update(
 | 
					      return await models.user.update({ where: { id: me.id }, data: { lastSeenAt: new Date() } })
 | 
				
			||||||
        { where: { id: me.id }, data: { lastSeenAt: new Date() } }
 | 
					 | 
				
			||||||
      ).catch(console.log)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return await models.user.findUnique({ where: { id: me.id } })
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    user: async (parent, { name }, { models }) => {
 | 
					    user: async (parent, { name }, { models }) => {
 | 
				
			||||||
      return await models.user.findUnique({ where: { name } })
 | 
					      return await models.user.findUnique({ where: { name } })
 | 
				
			||||||
@ -48,7 +44,9 @@ export default {
 | 
				
			|||||||
        throw new AuthenticationError('you must be logged in')
 | 
					        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 }) => {
 | 
					    topUsers: async (parent, { cursor, within, userType }, { models, me }) => {
 | 
				
			||||||
      const decodedCursor = decodeCursor(cursor)
 | 
					      const decodedCursor = decodeCursor(cursor)
 | 
				
			||||||
@ -136,7 +134,7 @@ export default {
 | 
				
			|||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        ([item] = await serialize(models,
 | 
					        ([item] = await serialize(models,
 | 
				
			||||||
          models.$queryRaw(`${SELECT} FROM create_bio($1, $2, $3) AS "Item"`,
 | 
					          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)
 | 
					      await createMentions(item, models)
 | 
				
			||||||
 | 
				
			|||||||
@ -178,9 +178,11 @@ export default {
 | 
				
			|||||||
        throw new UserInputError('amount must be positive', { argumentName: 'amount' })
 | 
					        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
 | 
					      // set expires at to 3 hours into future
 | 
				
			||||||
      const expiresAt = new Date(new Date().setHours(new Date().getHours() + 3))
 | 
					      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 {
 | 
					      try {
 | 
				
			||||||
        const invoice = await createInvoice({
 | 
					        const invoice = await createInvoice({
 | 
				
			||||||
          description,
 | 
					          description,
 | 
				
			||||||
@ -271,10 +273,12 @@ async function createWithdrawal (parent, { invoice, maxFee }, { me, models, lnd
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  const msatsFee = Number(maxFee) * 1000
 | 
					  const msatsFee = Number(maxFee) * 1000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const user = await models.user.findUnique({ where: { id: me.id } })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // create withdrawl transactionally (id, bolt11, amount, fee)
 | 
					  // create withdrawl transactionally (id, bolt11, amount, fee)
 | 
				
			||||||
  const [withdrawl] = await serialize(models,
 | 
					  const [withdrawl] = await serialize(models,
 | 
				
			||||||
    models.$queryRaw`SELECT * FROM create_withdrawl(${decoded.id}, ${invoice},
 | 
					    models.$queryRaw`SELECT * FROM create_withdrawl(${decoded.id}, ${invoice},
 | 
				
			||||||
      ${Number(decoded.mtokens)}, ${msatsFee}, ${me.name})`)
 | 
					      ${Number(decoded.mtokens)}, ${msatsFee}, ${user.name})`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  payViaPaymentRequest({
 | 
					  payViaPaymentRequest({
 | 
				
			||||||
    lnd,
 | 
					    lnd,
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										21
									
								
								components/modal-button.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								components/modal-button.js
									
									
									
									
									
										Normal 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>
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -10,9 +10,11 @@ import { gql, useApolloClient, useMutation } from '@apollo/client'
 | 
				
			|||||||
import styles from './user-header.module.css'
 | 
					import styles from './user-header.module.css'
 | 
				
			||||||
import { useMe } from './me'
 | 
					import { useMe } from './me'
 | 
				
			||||||
import { NAME_MUTATION, NAME_QUERY } from '../fragments/users'
 | 
					import { NAME_MUTATION, NAME_QUERY } from '../fragments/users'
 | 
				
			||||||
import Image from 'next/image'
 | 
					// import Image from 'next/image'
 | 
				
			||||||
import QRCode from 'qrcode.react'
 | 
					import QRCode from 'qrcode.react'
 | 
				
			||||||
import LightningIcon from '../svgs/bolt.svg'
 | 
					import LightningIcon from '../svgs/bolt.svg'
 | 
				
			||||||
 | 
					import ModalButton from './modal-button'
 | 
				
			||||||
 | 
					import { encodeLNUrl } from '../lib/lnurl'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function UserHeader ({ user }) {
 | 
					export default function UserHeader ({ user }) {
 | 
				
			||||||
  const [editting, setEditting] = useState(false)
 | 
					  const [editting, setEditting] = useState(false)
 | 
				
			||||||
@ -22,7 +24,7 @@ export default function UserHeader ({ user }) {
 | 
				
			|||||||
  const [setName] = useMutation(NAME_MUTATION)
 | 
					  const [setName] = useMutation(NAME_MUTATION)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const isMe = me?.name === user.name
 | 
					  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({
 | 
					  const UserSchema = Yup.object({
 | 
				
			||||||
    name: Yup.string()
 | 
					    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 (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      <div className='d-flex align-items-center mt-2 flex-wrap'>
 | 
					      <div className='d-flex align-items-center mt-2 flex-wrap'>
 | 
				
			||||||
        <Image
 | 
					        {/* <Image
 | 
				
			||||||
          src='/dorian400.jpg' width='200' height='166' layout='fixed'
 | 
					          src='/dorian400.jpg' width='135' height='135' layout='fixed'
 | 
				
			||||||
          className={styles.userimg}
 | 
					          className={styles.userimg}
 | 
				
			||||||
        />
 | 
					        /> */}
 | 
				
			||||||
        <div className='ml-3'>
 | 
					        <div>
 | 
				
			||||||
          {editting
 | 
					          {editting
 | 
				
			||||||
            ? (
 | 
					            ? (
 | 
				
			||||||
              <Form
 | 
					              <Form
 | 
				
			||||||
@ -85,7 +89,7 @@ export default function UserHeader ({ user }) {
 | 
				
			|||||||
                  setEditting(false)
 | 
					                  setEditting(false)
 | 
				
			||||||
                }}
 | 
					                }}
 | 
				
			||||||
              >
 | 
					              >
 | 
				
			||||||
                <div className='d-flex align-items-center mb-1'>
 | 
					                <div className='d-flex align-items-center mb-2'>
 | 
				
			||||||
                  <Input
 | 
					                  <Input
 | 
				
			||||||
                    prepend=<InputGroup.Text>@</InputGroup.Text>
 | 
					                    prepend=<InputGroup.Text>@</InputGroup.Text>
 | 
				
			||||||
                    name='name'
 | 
					                    name='name'
 | 
				
			||||||
@ -98,13 +102,15 @@ export default function UserHeader ({ user }) {
 | 
				
			|||||||
              </Form>
 | 
					              </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>
 | 
					                <div className={styles.username}>@{user.name}</div>
 | 
				
			||||||
                {isMe &&
 | 
					                {isMe &&
 | 
				
			||||||
                  <Button className='py-0' variant='link' onClick={() => setEditting(true)}>edit nym</Button>}
 | 
					                  <Button className='py-0' variant='link' onClick={() => setEditting(true)}>edit nym</Button>}
 | 
				
			||||||
              </div>
 | 
					              </div>
 | 
				
			||||||
              )}
 | 
					              )}
 | 
				
			||||||
          <Satistics user={user} />
 | 
					          <Satistics user={user} />
 | 
				
			||||||
 | 
					          <ModalButton
 | 
				
			||||||
 | 
					            clicker={
 | 
				
			||||||
              <Button className='font-weight-bold'>
 | 
					              <Button className='font-weight-bold'>
 | 
				
			||||||
                <LightningIcon
 | 
					                <LightningIcon
 | 
				
			||||||
                  width={20}
 | 
					                  width={20}
 | 
				
			||||||
@ -112,8 +118,14 @@ export default function UserHeader ({ user }) {
 | 
				
			|||||||
                  className='mr-1'
 | 
					                  className='mr-1'
 | 
				
			||||||
                />{user.name}@stacker.news
 | 
					                />{user.name}@stacker.news
 | 
				
			||||||
              </Button>
 | 
					              </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>
 | 
					        </div>
 | 
				
			||||||
        <QRCode className='ml-auto' value='fdsajfkldsajlkfjdlksajfkldjsalkjfdklsa' renderAs='svg' size={166} />
 | 
					 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <Nav
 | 
					      <Nav
 | 
				
			||||||
        className={styles.nav}
 | 
					        className={styles.nav}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										16
									
								
								lib/lnurl.js
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								lib/lnurl.js
									
									
									
									
									
								
							@ -1,19 +1,9 @@
 | 
				
			|||||||
import { randomBytes, createHash } from 'crypto'
 | 
					import { createHash } from 'crypto'
 | 
				
			||||||
import { bech32 } from 'bech32'
 | 
					import { bech32 } from 'bech32'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function lnurlAuth (params) {
 | 
					export function encodeLNUrl (url) {
 | 
				
			||||||
  // 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
 | 
					 | 
				
			||||||
  const words = bech32.toWords(Buffer.from(url.toString(), 'utf8'))
 | 
					  const words = bech32.toWords(Buffer.from(url.toString(), 'utf8'))
 | 
				
			||||||
  const encodedUrl = bech32.encode('lnurl', words, 1023)
 | 
					  return bech32.encode('lnurl', words, 1023)
 | 
				
			||||||
  return { secret, encodedUrl }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function lnurlPayMetadataString (username) {
 | 
					export function lnurlPayMetadataString (username) {
 | 
				
			||||||
 | 
				
			|||||||
@ -22,13 +22,6 @@ const options = {
 | 
				
			|||||||
        token.id = user.id
 | 
					        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
 | 
					      // sign them up for the newsletter
 | 
				
			||||||
      if (isNewUser && profile.email) {
 | 
					      if (isNewUser && profile.email) {
 | 
				
			||||||
        fetch(process.env.LIST_MONK_URL + '/api/subscribers', {
 | 
					        fetch(process.env.LIST_MONK_URL + '/api/subscribers', {
 | 
				
			||||||
@ -52,7 +45,6 @@ const options = {
 | 
				
			|||||||
    async session (session, token) {
 | 
					    async session (session, token) {
 | 
				
			||||||
      // we need to add additional session params here
 | 
					      // we need to add additional session params here
 | 
				
			||||||
      session.user.id = token.id
 | 
					      session.user.id = token.id
 | 
				
			||||||
      session.user.name = token.name
 | 
					 | 
				
			||||||
      return session
 | 
					      return session
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
				
			|||||||
@ -38,7 +38,7 @@ const apolloServer = new ApolloServer({
 | 
				
			|||||||
      models,
 | 
					      models,
 | 
				
			||||||
      lnd,
 | 
					      lnd,
 | 
				
			||||||
      me: session
 | 
					      me: session
 | 
				
			||||||
        ? await models.user.findUnique({ where: { id: session.user?.id } })
 | 
					        ? session.user
 | 
				
			||||||
        : null,
 | 
					        : null,
 | 
				
			||||||
      search
 | 
					      search
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user