stacker.news/pages/wallet.js

304 lines
8.4 KiB
JavaScript
Raw Normal View History

2021-05-06 21:15:22 +00:00
import { useRouter } from 'next/router'
import { Form, Input, SubmitButton } from '../components/form'
import Link from 'next/link'
import Button from 'react-bootstrap/Button'
import * as Yup from 'yup'
2021-10-28 19:59:53 +00:00
import { gql, useMutation, useQuery } from '@apollo/client'
import LnQR, { LnQRSkeleton } from '../components/lnqr'
2021-05-06 21:15:22 +00:00
import LayoutCenter from '../components/layout-center'
2021-05-13 13:28:38 +00:00
import InputGroup from 'react-bootstrap/InputGroup'
2021-08-19 21:42:21 +00:00
import { WithdrawlSkeleton } from './withdrawals/[id]'
2021-07-15 16:42:02 +00:00
import { useMe } from '../components/me'
2021-10-08 14:35:57 +00:00
import { useEffect, useState } from 'react'
2021-09-07 17:52:59 +00:00
import { requestProvider } from 'webln'
2021-10-08 14:35:57 +00:00
import { Alert } from 'react-bootstrap'
2022-01-23 17:21:55 +00:00
import { CREATE_WITHDRAWL, SEND_TO_LNADDR } from '../fragments/wallet'
2022-04-21 22:50:02 +00:00
import { getGetServerSideProps } from '../api/ssrApollo'
export const getServerSideProps = getGetServerSideProps()
2021-05-06 21:15:22 +00:00
export default function Wallet () {
return (
<LayoutCenter>
<WalletForm />
</LayoutCenter>
)
}
2021-07-15 16:42:02 +00:00
function YouHaveSats () {
const me = useMe()
return (
<h2 className={`${me ? 'visible' : 'invisible'} text-success pb-5`}>
you have <span className='text-monospace'>{me && me.sats}</span> sats
</h2>
)
}
2021-12-16 20:17:50 +00:00
function WalletHistory () {
return (
<div className='pt-4'>
<Link href='/satistics?inc=invoice,withdrawal' passHref>
<a className='text-muted font-weight-bold text-underline'>wallet history</a>
</Link>
</div>
)
}
2021-05-06 21:15:22 +00:00
export function WalletForm () {
const router = useRouter()
2021-12-16 20:17:50 +00:00
2021-05-06 21:15:22 +00:00
if (!router.query.type) {
return (
2021-09-02 17:22:48 +00:00
<div className='align-items-center text-center'>
2021-07-15 16:42:02 +00:00
<YouHaveSats />
2021-05-06 21:15:22 +00:00
<Link href='/wallet?type=fund'>
<Button variant='success'>fund</Button>
</Link>
<span className='mx-3 font-weight-bold text-muted'>or</span>
2021-08-19 21:42:21 +00:00
<Link href='/wallet?type=withdraw'>
<Button variant='success'>withdraw</Button>
2021-05-06 21:15:22 +00:00
</Link>
2021-12-16 20:17:50 +00:00
<WalletHistory />
2021-05-06 21:15:22 +00:00
</div>
)
}
if (router.query.type === 'fund') {
return <FundForm />
2021-10-28 19:59:53 +00:00
} else if (router.query.type === 'withdraw') {
2021-05-06 21:15:22 +00:00
return <WithdrawlForm />
2022-01-23 17:21:55 +00:00
} else if (router.query.type === 'lnurl-withdraw') {
2021-10-28 19:59:53 +00:00
return <LnWithdrawal />
2022-01-23 17:21:55 +00:00
} else {
return <LnAddrWithdrawal />
2021-05-06 21:15:22 +00:00
}
}
export const FundSchema = Yup.object({
2021-05-13 01:51:37 +00:00
amount: Yup.number().typeError('must be a number').required('required')
.positive('must be positive').integer('must be whole')
2021-05-06 21:15:22 +00:00
})
export function FundForm () {
2021-10-08 14:35:57 +00:00
const me = useMe()
const [showAlert, setShowAlert] = useState(true)
2021-05-06 21:15:22 +00:00
const router = useRouter()
2021-05-13 21:19:51 +00:00
const [createInvoice, { called, error }] = useMutation(gql`
2021-05-06 21:15:22 +00:00
mutation createInvoice($amount: Int!) {
2021-05-11 15:52:50 +00:00
createInvoice(amount: $amount) {
id
}
2021-05-06 21:15:22 +00:00
}`)
2021-10-08 14:35:57 +00:00
useEffect(() => {
2021-10-08 14:40:35 +00:00
setShowAlert(!localStorage.getItem('hideLnAddrAlert'))
2021-10-08 14:35:57 +00:00
}, [])
2021-05-13 21:19:51 +00:00
if (called && !error) {
2021-06-27 03:09:39 +00:00
return <LnQRSkeleton status='generating' />
2021-05-06 21:15:22 +00:00
}
return (
2021-07-15 16:42:02 +00:00
<>
<YouHaveSats />
2021-10-08 14:35:57 +00:00
{me && showAlert &&
<Alert
variant='success' dismissible onClose={() => {
localStorage.setItem('hideLnAddrAlert', 'yep')
setShowAlert(false)
}}
>
2022-03-17 18:04:53 +00:00
You can also fund your account via lightning address with <strong>{`${me.name}@stacker.news`}</strong>
2021-10-08 14:35:57 +00:00
</Alert>}
2021-07-15 16:42:02 +00:00
<Form
initial={{
amount: 1000
}}
schema={FundSchema}
onSubmit={async ({ amount }) => {
const { data } = await createInvoice({ variables: { amount: Number(amount) } })
router.push(`/invoices/${data.createInvoice.id}`)
}}
>
<Input
label='amount'
name='amount'
required
autoFocus
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
/>
<SubmitButton variant='success' className='mt-2'>generate invoice</SubmitButton>
</Form>
2021-12-16 20:17:50 +00:00
<WalletHistory />
2021-07-15 16:42:02 +00:00
</>
2021-05-06 21:15:22 +00:00
)
}
2021-05-12 23:04:19 +00:00
export const WithdrawlSchema = Yup.object({
invoice: Yup.string().required('required'),
2021-05-13 01:51:37 +00:00
maxFee: Yup.number().typeError('must be a number').required('required')
.min(0, 'must be positive').integer('must be whole')
2021-05-12 23:04:19 +00:00
})
2021-09-07 17:52:59 +00:00
const MAX_FEE_DEFAULT = 10
2021-05-06 21:15:22 +00:00
export function WithdrawlForm () {
2021-05-13 01:51:37 +00:00
const router = useRouter()
2021-09-07 17:52:59 +00:00
const me = useMe()
2021-05-12 23:04:19 +00:00
2021-10-28 19:59:53 +00:00
const [createWithdrawl, { called, error }] = useMutation(CREATE_WITHDRAWL)
2021-05-12 23:04:19 +00:00
2021-09-07 17:52:59 +00:00
useEffect(async () => {
try {
const provider = await requestProvider()
const { paymentRequest: invoice } = await provider.makeInvoice({
defaultMemo: `Withdrawal for @${me.name} on SN`
})
const { data } = await createWithdrawl({ variables: { invoice, maxFee: MAX_FEE_DEFAULT } })
router.push(`/withdrawals/${data.createWithdrawl.id}`)
} catch (e) {
2021-09-07 18:04:56 +00:00
console.log(e.message)
2021-09-07 17:52:59 +00:00
}
}, [])
2021-05-13 21:19:51 +00:00
if (called && !error) {
return <WithdrawlSkeleton status='sending' />
}
2021-05-12 23:04:19 +00:00
return (
<>
2021-07-15 16:42:02 +00:00
<YouHaveSats />
2021-05-12 23:04:19 +00:00
<Form
initial={{
2021-05-13 01:51:37 +00:00
invoice: '',
2021-09-07 17:52:59 +00:00
maxFee: MAX_FEE_DEFAULT
2021-05-12 23:04:19 +00:00
}}
2021-05-13 21:19:51 +00:00
initialError={error ? error.toString() : undefined}
2021-05-12 23:04:19 +00:00
schema={WithdrawlSchema}
onSubmit={async ({ invoice, maxFee }) => {
2021-05-13 01:51:37 +00:00
const { data } = await createWithdrawl({ variables: { invoice, maxFee: Number(maxFee) } })
2021-08-19 21:42:21 +00:00
router.push(`/withdrawals/${data.createWithdrawl.id}`)
2021-05-12 23:04:19 +00:00
}}
>
<Input
label='invoice'
name='invoice'
required
autoFocus
/>
<Input
label='max fee'
name='maxFee'
required
2021-05-13 21:19:51 +00:00
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
2021-05-12 23:04:19 +00:00
/>
2021-08-19 21:42:21 +00:00
<SubmitButton variant='success' className='mt-2'>withdraw</SubmitButton>
2021-05-12 23:04:19 +00:00
</Form>
2022-01-23 17:21:55 +00:00
<span className='my-3 font-weight-bold text-muted'>or via</span>
2021-10-28 19:59:53 +00:00
<Link href='/wallet?type=lnurl-withdraw'>
<Button variant='grey'>QR code</Button>
</Link>
2022-01-23 17:21:55 +00:00
<Link href='/wallet?type=lnaddr-withdraw'>
<Button className='mt-2' variant='grey'>Lightning Address</Button>
</Link>
2021-12-16 20:17:50 +00:00
<WalletHistory />
2021-05-12 23:04:19 +00:00
</>
)
2021-05-06 21:15:22 +00:00
}
2021-10-28 19:59:53 +00:00
function LnQRWith ({ k1, encodedUrl }) {
const router = useRouter()
const query = gql`
{
lnWith(k1: "${k1}") {
withdrawalId
k1
}
}`
const { data } = useQuery(query, { pollInterval: 1000, fetchPolicy: 'cache-first' })
if (data?.lnWith?.withdrawalId) {
router.push(`/withdrawals/${data.lnWith.withdrawalId}`)
}
return <LnQR value={encodedUrl} status='waiting for you' />
}
export function LnWithdrawal () {
// query for challenge
const [createAuth, { data, error }] = useMutation(gql`
mutation createAuth {
createWith {
k1
encodedUrl
}
}`)
useEffect(createAuth, [])
if (error) return <div>error</div>
if (!data) {
return <LnQRSkeleton status='generating' />
}
return <LnQRWith {...data.createWith} />
}
2022-01-23 17:21:55 +00:00
export const LnAddrSchema = Yup.object({
// addr: Yup.string().email('address is no good').required('required'),
amount: Yup.number().typeError('must be a number').required('required')
.positive('must be positive').integer('must be whole'),
maxFee: Yup.number().typeError('must be a number').required('required')
.min(0, 'must be positive').integer('must be whole')
})
export function LnAddrWithdrawal () {
const router = useRouter()
const [sendToLnAddr, { called, error }] = useMutation(SEND_TO_LNADDR)
if (called && !error) {
return <WithdrawlSkeleton status='sending' />
}
return (
<>
<YouHaveSats />
<Form
initial={{
addr: '',
amount: 1,
maxFee: 10
}}
schema={LnAddrSchema}
initialError={error ? error.toString() : undefined}
onSubmit={async ({ addr, amount, maxFee }) => {
2022-02-02 20:56:14 +00:00
const { data } = await sendToLnAddr({ variables: { addr, amount: Number(amount), maxFee: Number(maxFee) } })
2022-01-23 17:21:55 +00:00
router.push(`/withdrawals/${data.sendToLnAddr.id}`)
}}
>
<Input
label='lightning address'
name='addr'
required
autoFocus
/>
<Input
label='amount'
name='amount'
required
autoFocus
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
/>
<Input
label='max fee'
name='maxFee'
required
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
/>
<SubmitButton variant='success' className='mt-2'>send</SubmitButton>
</Form>
</>
)
}