satisitics with invoice & withdrawal
This commit is contained in:
parent
8cdeb18216
commit
06f5ed731e
@ -2,6 +2,7 @@ import { createInvoice, decodePaymentRequest, subscribeToPayViaRequest } from 'l
|
||||
import { UserInputError, AuthenticationError } from 'apollo-server-micro'
|
||||
import serialize from './serial'
|
||||
import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor'
|
||||
import lnpr from 'bolt11'
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
@ -50,19 +51,19 @@ export default {
|
||||
},
|
||||
walletHistory: async (parent, { cursor }, { me, models, lnd }) => {
|
||||
const decodedCursor = decodeCursor(cursor)
|
||||
// if (!me) {
|
||||
// throw new AuthenticationError('you must be logged in')
|
||||
// }
|
||||
if (!me) {
|
||||
throw new AuthenticationError('you must be logged in')
|
||||
}
|
||||
|
||||
// TODO
|
||||
// 1. union invoices and withdrawals
|
||||
// 1. union invoices and withdrawals (check)
|
||||
// 2. add to union spending and receiving
|
||||
|
||||
const history = await models.$queryRaw(`
|
||||
let history = await models.$queryRaw(`
|
||||
(SELECT id, bolt11, created_at as "createdAt",
|
||||
"msatsReceived" as msats, NULL as "msatsFee",
|
||||
COALESCE("msatsReceived", "msatsRequested") as msats, NULL as "msatsFee",
|
||||
CASE WHEN "confirmedAt" IS NOT NULL THEN 'CONFIRMED'
|
||||
WHEN "expiresAt" IS NOT NULL THEN 'EXPIRED'
|
||||
WHEN "expiresAt" <= $2 THEN 'EXPIRED'
|
||||
WHEN cancelled THEN 'CANCELLED'
|
||||
ELSE 'PENDING' END as status,
|
||||
'invoice' as type
|
||||
@ -86,7 +87,31 @@ export default {
|
||||
LIMIT ${LIMIT}+$3)
|
||||
ORDER BY "createdAt" DESC
|
||||
OFFSET $3
|
||||
LIMIT ${LIMIT}`, 624, decodedCursor.time, decodedCursor.offset)
|
||||
LIMIT ${LIMIT}`, me.id, decodedCursor.time, decodedCursor.offset)
|
||||
|
||||
history = history.map(f => {
|
||||
if (f.bolt11) {
|
||||
const inv = lnpr.decode(f.bolt11)
|
||||
if (inv) {
|
||||
const { tags } = inv
|
||||
for (const tag of tags) {
|
||||
if (tag.tagName === 'description') {
|
||||
f.description = tag.data
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (f.type) {
|
||||
case 'withdrawal':
|
||||
f.msats *= -1
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return f
|
||||
})
|
||||
|
||||
return {
|
||||
cursor: history.length === LIMIT ? nextCursorEncoded(decodedCursor) : null,
|
||||
|
@ -41,12 +41,13 @@ export default gql`
|
||||
|
||||
type Fact {
|
||||
id: ID!
|
||||
bolt11: String!
|
||||
bolt11: String
|
||||
createdAt: String!
|
||||
msats: Int!
|
||||
msatsFee: Int!
|
||||
msatsFee: Int
|
||||
status: String!
|
||||
type: String!
|
||||
description: String
|
||||
}
|
||||
|
||||
type History {
|
||||
|
@ -32,6 +32,7 @@ export default function UserHeader ({ user }) {
|
||||
const [setName] = useMutation(NAME_MUTATION)
|
||||
|
||||
const Satistics = () => <h1 className='mb-0'><small className='text-success'>{user.sats} sats \ {user.stacked} stacked</small></h1>
|
||||
const isMe = me?.name === user.name
|
||||
|
||||
const UserSchema = Yup.object({
|
||||
name: Yup.string()
|
||||
@ -104,7 +105,7 @@ export default function UserHeader ({ user }) {
|
||||
: (
|
||||
<div className='d-flex align-items-center'>
|
||||
<h2 className='mb-0'>@{user.name}</h2>
|
||||
{me?.name === user.name &&
|
||||
{isMe &&
|
||||
<Button variant='link' onClick={() => setEditting(true)}>edit nym</Button>}
|
||||
</div>
|
||||
)}
|
||||
@ -129,11 +130,12 @@ export default function UserHeader ({ user }) {
|
||||
<Nav.Link>{user.ncomments} comments</Nav.Link>
|
||||
</Link>
|
||||
</Nav.Item>
|
||||
{/* <Nav.Item>
|
||||
<Link href={'/' + user.name + '/sativity'} passHref>
|
||||
<Nav.Link>satistics</Nav.Link>
|
||||
</Link>
|
||||
</Nav.Item> */}
|
||||
{isMe &&
|
||||
<Nav.Item>
|
||||
<Link href='/satistics' passHref>
|
||||
<Nav.Link>satistics</Nav.Link>
|
||||
</Link>
|
||||
</Nav.Item>}
|
||||
</Nav>
|
||||
</>
|
||||
)
|
||||
|
@ -24,6 +24,24 @@ export const WITHDRAWL = gql`
|
||||
}
|
||||
}`
|
||||
|
||||
export const WALLET_HISTORY = gql`
|
||||
query WalletHistory($cursor: String) {
|
||||
walletHistory(cursor: $cursor) {
|
||||
facts {
|
||||
id
|
||||
type
|
||||
createdAt
|
||||
msats
|
||||
msatsFee
|
||||
status
|
||||
type
|
||||
description
|
||||
}
|
||||
cursor
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const CREATE_WITHDRAWL = gql`
|
||||
mutation createWithdrawl($invoice: String!, $maxFee: Int!) {
|
||||
createWithdrawl(invoice: $invoice, maxFee: $maxFee) {
|
||||
|
@ -64,6 +64,19 @@ export default function getApolloClient () {
|
||||
lastChecked: incoming.lastChecked
|
||||
}
|
||||
}
|
||||
},
|
||||
walletHistory: {
|
||||
keyArgs: false,
|
||||
merge (existing, incoming) {
|
||||
if (isFirstPage(incoming.cursor, existing?.facts)) {
|
||||
return incoming
|
||||
}
|
||||
|
||||
return {
|
||||
cursor: incoming.cursor,
|
||||
facts: [...(existing?.facts || []), ...incoming.facts]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
112
package-lock.json
generated
112
package-lock.json
generated
@ -14,6 +14,7 @@
|
||||
"async-retry": "^1.3.1",
|
||||
"babel-plugin-inline-react-svg": "^2.0.1",
|
||||
"bech32": "^2.0.0",
|
||||
"bolt11": "^1.3.4",
|
||||
"bootstrap": "^4.6.0",
|
||||
"clipboard-copy": "^4.0.1",
|
||||
"domino": "^2.1.6",
|
||||
@ -910,6 +911,14 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/bn.js": {
|
||||
"version": "4.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz",
|
||||
"integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/body-parser": {
|
||||
"version": "1.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz",
|
||||
@ -2096,6 +2105,53 @@
|
||||
"node": ">=10.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bolt11": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/bolt11/-/bolt11-1.3.4.tgz",
|
||||
"integrity": "sha512-x4lHDv0oid13lGlZU7cl/5gx9nRwjB2vgK/uB3c50802Wh+9WjWQMwzD2PCETHylUijx2iBAqUQYbx3ZgwF06Q==",
|
||||
"dependencies": {
|
||||
"@types/bn.js": "^4.11.3",
|
||||
"bech32": "^1.1.2",
|
||||
"bitcoinjs-lib": "^6.0.0",
|
||||
"bn.js": "^4.11.8",
|
||||
"create-hash": "^1.2.0",
|
||||
"lodash": "^4.17.11",
|
||||
"safe-buffer": "^5.1.1",
|
||||
"secp256k1": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/bolt11/node_modules/bech32": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz",
|
||||
"integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ=="
|
||||
},
|
||||
"node_modules/bolt11/node_modules/bitcoinjs-lib": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.0.1.tgz",
|
||||
"integrity": "sha512-x/7D4jDj/MMkmO6t3p2CSDXTqpwZ/jRsRiJDmaiXabrR9XRo7jwby8HRn7EyK1h24rKFFI7vI0ay4czl6bDOZQ==",
|
||||
"dependencies": {
|
||||
"bech32": "^2.0.0",
|
||||
"bip174": "^2.0.1",
|
||||
"bs58check": "^2.1.2",
|
||||
"create-hash": "^1.1.0",
|
||||
"typeforce": "^1.11.3",
|
||||
"varuint-bitcoin": "^1.1.2",
|
||||
"wif": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bolt11/node_modules/bitcoinjs-lib/node_modules/bech32": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz",
|
||||
"integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg=="
|
||||
},
|
||||
"node_modules/bolt11/node_modules/bn.js": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||
},
|
||||
"node_modules/boolbase": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||
@ -12218,6 +12274,14 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/bn.js": {
|
||||
"version": "4.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz",
|
||||
"integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==",
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/body-parser": {
|
||||
"version": "1.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.1.tgz",
|
||||
@ -13215,6 +13279,54 @@
|
||||
"resolved": "https://registry.npmjs.org/bolt09/-/bolt09-0.1.5.tgz",
|
||||
"integrity": "sha512-oT1+erg21vat55oXNd7nNEkCO0FQnmaraFZuyXFyeVk7dZCm/3vgic0qK1VuUSV+ksYXJfRKYC4AqfYrtHNPZg=="
|
||||
},
|
||||
"bolt11": {
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/bolt11/-/bolt11-1.3.4.tgz",
|
||||
"integrity": "sha512-x4lHDv0oid13lGlZU7cl/5gx9nRwjB2vgK/uB3c50802Wh+9WjWQMwzD2PCETHylUijx2iBAqUQYbx3ZgwF06Q==",
|
||||
"requires": {
|
||||
"@types/bn.js": "^4.11.3",
|
||||
"bech32": "^1.1.2",
|
||||
"bitcoinjs-lib": "^6.0.0",
|
||||
"bn.js": "^4.11.8",
|
||||
"create-hash": "^1.2.0",
|
||||
"lodash": "^4.17.11",
|
||||
"safe-buffer": "^5.1.1",
|
||||
"secp256k1": "^4.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"bech32": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz",
|
||||
"integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ=="
|
||||
},
|
||||
"bitcoinjs-lib": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.0.1.tgz",
|
||||
"integrity": "sha512-x/7D4jDj/MMkmO6t3p2CSDXTqpwZ/jRsRiJDmaiXabrR9XRo7jwby8HRn7EyK1h24rKFFI7vI0ay4czl6bDOZQ==",
|
||||
"requires": {
|
||||
"bech32": "^2.0.0",
|
||||
"bip174": "^2.0.1",
|
||||
"bs58check": "^2.1.2",
|
||||
"create-hash": "^1.1.0",
|
||||
"typeforce": "^1.11.3",
|
||||
"varuint-bitcoin": "^1.1.2",
|
||||
"wif": "^2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"bech32": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz",
|
||||
"integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"bn.js": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"boolbase": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||
|
@ -15,6 +15,7 @@
|
||||
"async-retry": "^1.3.1",
|
||||
"babel-plugin-inline-react-svg": "^2.0.1",
|
||||
"bech32": "^2.0.0",
|
||||
"bolt11": "^1.3.4",
|
||||
"bootstrap": "^4.6.0",
|
||||
"clipboard-copy": "^4.0.1",
|
||||
"domino": "^2.1.6",
|
||||
|
128
pages/satistics.js
Normal file
128
pages/satistics.js
Normal file
@ -0,0 +1,128 @@
|
||||
import { useQuery } from '@apollo/client'
|
||||
import Link from 'next/link'
|
||||
import { Table } from 'react-bootstrap'
|
||||
import useDarkMode from 'use-dark-mode'
|
||||
import { getGetServerSideProps } from '../api/ssrApollo'
|
||||
import Layout from '../components/layout'
|
||||
import { useMe } from '../components/me'
|
||||
import MoreFooter from '../components/more-footer'
|
||||
import UserHeader from '../components/user-header'
|
||||
import { WALLET_HISTORY } from '../fragments/wallet'
|
||||
import styles from '../styles/satistics.module.css'
|
||||
import Moon from '../svgs/moon-fill.svg'
|
||||
import Check from '../svgs/check-double-line.svg'
|
||||
import ThumbDown from '../svgs/thumb-down-fill.svg'
|
||||
|
||||
export const getServerSideProps = getGetServerSideProps(WALLET_HISTORY)
|
||||
|
||||
function satusClass (status) {
|
||||
switch (status) {
|
||||
case 'CONFIRMED':
|
||||
return ''
|
||||
case 'PENDING':
|
||||
return 'text-muted'
|
||||
default:
|
||||
return styles.failed
|
||||
}
|
||||
}
|
||||
|
||||
function Satus ({ status }) {
|
||||
if (!status) {
|
||||
return null
|
||||
}
|
||||
|
||||
const desc = () => {
|
||||
switch (status) {
|
||||
case 'CONFIRMED':
|
||||
return 'confirmed'
|
||||
case 'EXPIRED':
|
||||
return 'expired'
|
||||
case 'INSUFFICIENT_BALANCE':
|
||||
return "you didn't have enough sats"
|
||||
case 'INVALID_PAYMENT':
|
||||
return 'invalid payment'
|
||||
case 'PATHFINDING_TIMEOUT':
|
||||
case 'ROUTE_NOT_FOUND':
|
||||
return 'no route found'
|
||||
case 'PENDING':
|
||||
return 'pending'
|
||||
default:
|
||||
return 'unknown failure'
|
||||
}
|
||||
}
|
||||
|
||||
const color = () => {
|
||||
switch (status) {
|
||||
case 'CONFIRMED':
|
||||
return 'success'
|
||||
case 'PENDING':
|
||||
return 'muted'
|
||||
default:
|
||||
return 'danger'
|
||||
}
|
||||
}
|
||||
|
||||
const Icon = () => {
|
||||
switch (status) {
|
||||
case 'CONFIRMED':
|
||||
return <Check width='14' height='14' className='fill-success' />
|
||||
case 'PENDING':
|
||||
return <Moon width='14' height='14' className='spin fill-grey' />
|
||||
default:
|
||||
return <ThumbDown width='14' height='14' className='fill-danger' />
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Icon /><small className={`text-${color()}`}>{' ' + desc()}</small>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function Satistics ({ data: { walletHistory: { facts, cursor } } }) {
|
||||
const me = useMe()
|
||||
const { value: darkMode } = useDarkMode()
|
||||
const { data, fetchMore } = useQuery(WALLET_HISTORY)
|
||||
|
||||
if (data) {
|
||||
({ walletHistory: { facts, cursor } } = data)
|
||||
}
|
||||
|
||||
const SatisticsSkeleton = () => (
|
||||
<div className='d-flex justify-content-center mt-3 mb-1'>
|
||||
<Moon className='spin fill-grey' />
|
||||
</div>)
|
||||
|
||||
return (
|
||||
<Layout noSeo>
|
||||
<UserHeader user={me} />
|
||||
<Table className='mt-3 mb-0' bordered hover size='sm' variant={darkMode ? 'dark' : undefined}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className={styles.type}>type</th>
|
||||
<th>detail</th>
|
||||
<th className={styles.sats}>sats</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{facts.map((f, i) => (
|
||||
<Link href={`${f.type}s/${f.id}`} key={`${f.type}-${f.id}`}>
|
||||
<tr className={styles.row}>
|
||||
<td className={`${styles.type} ${satusClass(f.status)}`}>{f.type}</td>
|
||||
<td className={styles.description}>
|
||||
<div className={satusClass(f.status)}>
|
||||
{f.description || 'no description'}
|
||||
</div>
|
||||
<Satus status={f.status} />
|
||||
</td>
|
||||
<td className={`${styles.sats} ${satusClass(f.status)}`}>{f.msats / 1000}</td>
|
||||
</tr>
|
||||
</Link>
|
||||
))}
|
||||
</tbody>
|
||||
</Table>
|
||||
<MoreFooter cursor={cursor} fetchMore={fetchMore} Skeleton={SatisticsSkeleton} />
|
||||
</Layout>
|
||||
)
|
||||
}
|
@ -59,6 +59,20 @@ $tooltip-bg: #5c8001;
|
||||
src: url(/Lightningvolt-xoqm.ttf);
|
||||
}
|
||||
|
||||
.table-sm th, .table-sm td {
|
||||
padding: .3rem .75rem;
|
||||
line-height: 1.2rem;
|
||||
}
|
||||
|
||||
.table {
|
||||
color: var(--theme-color);
|
||||
background-color: var(--theme-body);
|
||||
}
|
||||
|
||||
.table th, .table td, .table thead th {
|
||||
border-color: var(--theme-borderColor);
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--theme-body);
|
||||
color: var(--theme-color);
|
||||
|
18
styles/satistics.module.css
Normal file
18
styles/satistics.module.css
Normal file
@ -0,0 +1,18 @@
|
||||
.type {
|
||||
width: 1px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sats {
|
||||
width: 1px;
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.failed {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.row {
|
||||
cursor: pointer;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user