Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 25 additions & 7 deletions components/Chart.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { NextPage } from 'next'
import React from 'react'
import { formatQuoteValue } from 'utils/index'
import { USD_QUOTE_ID } from 'constants/index'
import {
Chart as ChartJS,
CategoryScale,
Expand All @@ -13,6 +12,9 @@ import {
Legend
} from 'chart.js'
import { Line } from 'react-chartjs-2'
import { ChartData } from 'redis/types'
import { USD_QUOTE_ID } from 'constants/index'
import { QuoteValues } from 'services/priceService'

ChartJS.register(
CategoryScale,
Expand All @@ -25,13 +27,28 @@ ChartJS.register(
)

interface Props {
data: object
usd: boolean
chartData: ChartData
currencyId?: number
}

const Chart: NextPage<Props> = ({ data, usd }) => {
const chartData = data
function getQuoteOption (quoteValues: QuoteValues[], currencyId: number): number[] {
return quoteValues.map(qv => Number(currencyId === USD_QUOTE_ID ? qv.usd : qv.cad))
}

const Chart: NextPage<Props> = ({ chartData, currencyId }) => {
const data = chartData
if (chartData.isMultiQuote && currencyId !== undefined) {
if (data.datasets.length > 0) {
data.datasets = [
{
...data.datasets[0],
data: Array.isArray(data.datasets[0].data) && typeof data.datasets[0].data[0] === 'object'
? getQuoteOption(data.datasets[0].data as QuoteValues[], currencyId)
: data.datasets[0].data
}
]
}
}
function cssvar (name): string {
/*
if (typeof window !== "undefined") {
Expand All @@ -54,7 +71,8 @@ const Chart: NextPage<Props> = ({ data, usd }) => {
displayColors: false,
callbacks: {
label: function (context) {
return usd ? '$' + formatQuoteValue(context.raw, USD_QUOTE_ID) : formatQuoteValue(context.raw)
const prefix = currencyId !== undefined ? '$' : ''
return prefix + formatQuoteValue(context.raw, currencyId)
}
},
mode: 'nearest',
Expand Down Expand Up @@ -83,7 +101,7 @@ const Chart: NextPage<Props> = ({ data, usd }) => {
ticks: {
color: cssvar('--primary-text-color'),
callback: function (value: string) {
return usd ? '$' + formatQuoteValue(value, USD_QUOTE_ID) : value
return currencyId !== undefined ? '$' + formatQuoteValue(value, currencyId) : value
}
},
position: 'right'
Expand Down
6 changes: 3 additions & 3 deletions components/Dashboard/Leaderboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import { useMemo } from 'react'
import { PaymentDataByButton } from 'redis/types'
import style from '../Transaction/transaction.module.css'
import moment from 'moment'
import { USD_QUOTE_ID } from 'constants/index'
import { compareNumericString } from 'utils/index'
import { formatQuoteValue } from 'utils'

interface IProps {
buttons: PaymentDataByButton
totalString: string
currencyId: number
}

export default ({ buttons, totalString }: IProps): JSX.Element => {
export default ({ buttons, totalString, currencyId }: IProps): JSX.Element => {
const columns = useMemo(
() => [
{
Expand All @@ -39,7 +39,7 @@ export default ({ buttons, totalString }: IProps): JSX.Element => {
sortType: compareNumericString,
Cell: (cellProps) => {
return <div style={{ textAlign: 'right', fontWeight: '600' }}>
{'$'.concat(formatQuoteValue(cellProps.cell.value, USD_QUOTE_ID))}
{'$'.concat(formatQuoteValue(cellProps.cell.value, currencyId))}
</div>
}
},
Expand Down
7 changes: 4 additions & 3 deletions pages/api/paybutton/download/transactions/[paybuttonId].ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import {
NetworkTickersType,
NETWORK_TICKERS,
NETWORK_IDS,
SUPPORTED_QUOTES_FROM_ID,
USD_QUOTE_ID
SUPPORTED_QUOTES_FROM_ID
} from 'constants/index'
import { TransactionWithAddressAndPrices, fetchTransactionsByPaybuttonId, getTransactionValueInCurrency } from 'services/transactionService'
import { PaybuttonWithAddresses, fetchPaybuttonById } from 'services/paybuttonService'
Expand All @@ -20,6 +19,7 @@ import { setSession } from 'utils/setSession'
import { NextApiResponse } from 'next'
import { Decimal } from '@prisma/client/runtime'
import { getNetworkIdFromSlug } from 'services/networkService'
import { fetchUserProfileFromId } from 'services/userService'

export interface TransactionFileData {
amount: Decimal
Expand Down Expand Up @@ -144,13 +144,14 @@ export default async (req: any, res: any): Promise<void> => {
await setSession(req, res)

const userId = req.session.userId
const user = await fetchUserProfileFromId(userId)
const paybuttonId = req.query.paybuttonId as string
const networkTickerReq = req.query.network as string

const networkTicker = (networkTickerReq !== '' && isNetworkValid(networkTickerReq as NetworkTickersType)) ? networkTickerReq.toUpperCase() as NetworkTickersType : undefined
let quoteId: number
if (req.query.currency === undefined || req.query.currency === '' || Number.isNaN(req.query.currency)) {
quoteId = USD_QUOTE_ID
quoteId = user.preferredCurrencyId
} else {
quoteId = req.query.currency as number
}
Expand Down
12 changes: 6 additions & 6 deletions pages/dashboard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Session from 'supertokens-node/recipe/session'
import { GetServerSideProps } from 'next'
import style from './dashboard.module.css'
import { formatQuoteValue, removeUnserializableFields } from 'utils/index'
import { COOKIE_NAMES, USD_QUOTE_ID } from 'constants/index'
import { COOKIE_NAMES } from 'constants/index'
import Leaderboard from 'components/Dashboard/Leaderboard'
import { DashboardData, PeriodData } from 'redis/types'
import { loadStateFromCookie, saveStateToCookie } from 'utils/cookies'
Expand Down Expand Up @@ -123,7 +123,7 @@ export default function Dashboard ({ user }: PaybuttonsProps): React.ReactElemen
<>
<TopBar title="Dashboard" user={user.stUser?.email} />
<div className={style.number_ctn}>
<NumberBlock value={'$'.concat(formatQuoteValue(dashboardData.total.revenue, USD_QUOTE_ID)) } text='Revenue (lifetime)' />
<NumberBlock value={'$'.concat(formatQuoteValue(dashboardData.total.revenue, user.userProfile.preferredCurrencyId)) } text='Revenue (lifetime)' />
<NumberBlock value={formatQuoteValue(dashboardData.total.payments)} text='Payments (lifetime)' />
<NumberBlock value={dashboardData.total.buttons} text='Buttons' />
</div>
Expand All @@ -137,10 +137,10 @@ export default function Dashboard ({ user }: PaybuttonsProps): React.ReactElemen
<div className={style.chart_inner_ctn}>
<div className={style.chart_title_ctn}>
<h5>Revenue</h5>
<h5>{totalString}: ${formatQuoteValue(activePeriod.totalRevenue, USD_QUOTE_ID)}</h5>
<h5>{totalString}: ${formatQuoteValue(activePeriod.totalRevenue, user.userProfile.preferredCurrencyId)}</h5>
</div>
<div className={style.chart_ctn}>
<Chart data={activePeriod.revenue} usd={true} />
<Chart chartData={activePeriod.revenue} currencyId={user.userProfile.preferredCurrencyId} />
</div>
</div>
<div className={style.chart_inner_ctn}>
Expand All @@ -149,15 +149,15 @@ export default function Dashboard ({ user }: PaybuttonsProps): React.ReactElemen
<h5>{totalString}: {formatQuoteValue(activePeriod.totalPayments)}</h5>
</div>
<div className={style.chart_ctn}>
<Chart data={activePeriod.payments} />
<Chart chartData={activePeriod.payments} />
</div>
</div>
<div className={`${style.full_width} ${style.chart_inner_ctn}`}>
<div className={style.chart_title_ctn}>
<h5>Button Leaderboard</h5>
<div></div>
</div>
<Leaderboard totalString={totalString} buttons={activePeriod.buttons}/>
<Leaderboard totalString={totalString} buttons={activePeriod.buttons} currencyId={user.userProfile.preferredCurrencyId}/>
</div>
</div>
</>
Expand Down
6 changes: 3 additions & 3 deletions pages/payments/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import XECIcon from 'assets/xec-logo.png'
import BCHIcon from 'assets/bch-logo.png'
import EyeIcon from 'assets/eye-icon.png'
import { formatQuoteValue, compareNumericString, removeUnserializableFields } from 'utils/index'
import { XEC_NETWORK_ID, USD_QUOTE_ID, BCH_TX_EXPLORER_URL, XEC_TX_EXPLORER_URL } from 'constants/index'
import { XEC_NETWORK_ID, BCH_TX_EXPLORER_URL, XEC_TX_EXPLORER_URL } from 'constants/index'
import moment from 'moment'
import TopBar from 'components/TopBar'
import { fetchUserWithSupertokens, UserWithSupertokens } from 'services/userService'
Expand Down Expand Up @@ -71,10 +71,10 @@ export default function Payments ({ user }: PaybuttonsProps): React.ReactElement
},
{
Header: () => (<div style={{ textAlign: 'right' }}>Amount</div>),
accessor: 'value',
accessor: 'values',
sortType: compareNumericString,
Cell: (cellProps) => {
return <div style={{ textAlign: 'right', fontWeight: '600' }}>${formatQuoteValue(cellProps.cell.value, USD_QUOTE_ID)}</div>
return <div style={{ textAlign: 'right', fontWeight: '600' }}>${formatQuoteValue(cellProps.cell.value, user.userProfile.preferredCurrencyId)}</div>
}
},
{
Expand Down
16 changes: 9 additions & 7 deletions redis/balanceCache.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Prisma } from '@prisma/client'
import { AddressPaymentInfo, generateAddressPaymentInfo } from 'services/addressService'
import { AddressPaymentInfo, AddressWithTransactionsWithPrices, generateAddressPaymentInfo } from 'services/addressService'
import { TransactionWithAddressAndPrices } from 'services/transactionService'
import { redis } from './clientInstance'
import { Payment } from './types'

// ADDRESS:balance
const getBalanceKey = (addressString: string): string => {
Expand All @@ -14,13 +13,16 @@ export const cacheAddressPaymentInfo = async (addressString: string, info: Addre
await redis.set(balanceKey, JSON.stringify(info))
}

export const cacheBalanceFromPayments = async (addressString: string, payments: Payment[]): Promise<void> => {
const paymentsSum = payments.reduce((prev, curr) => prev.plus(curr.value), new Prisma.Decimal(0))
export const cacheBalanceForAddress = async (address: AddressWithTransactionsWithPrices): Promise<void> => {
let balance = new Prisma.Decimal(0)
for (const tx of address.transactions) {
balance = balance.plus(tx.amount)
}
const info: AddressPaymentInfo = {
balance: paymentsSum,
paymentCount: payments.length
balance,
paymentCount: address.transactions.filter(tx => tx.amount.gt(0)).length
}
await cacheAddressPaymentInfo(addressString, info)
await cacheAddressPaymentInfo(address.address, info)
}

export const getCachedBalanceForAddress = async (addressString: string): Promise<AddressPaymentInfo | null> => {
Expand Down
56 changes: 43 additions & 13 deletions redis/dashboardCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Prisma } from '@prisma/client'
import moment, { DurationInputArg2 } from 'moment'
import { XEC_NETWORK_ID, BCH_NETWORK_ID } from 'constants/index'
import { fetchPaybuttonArrayByUserId } from 'services/paybuttonService'
import { QuoteValues } from 'services/priceService'

// USERID:dashboard
const getDashboardSummaryKey = (userId: string): string => {
Expand All @@ -15,18 +16,21 @@ const getChartLabels = function (n: number, periodString: string, formatString =
return [...new Array(n)].map((_, idx) => moment().startOf('day').subtract(idx, periodString as DurationInputArg2).format(formatString)).reverse()
}

const getChartRevenuePaymentData = function (n: number, periodString: string, paymentList: Payment[]): any {
const revenueArray: Prisma.Decimal[] = []
interface RevenuePaymentData {
revenue: QuoteValues[]
payments: number[]
}

const getChartRevenuePaymentData = function (n: number, periodString: string, paymentList: Payment[]): RevenuePaymentData {
const revenueArray: QuoteValues[] = []
const paymentsArray: number[] = []
const _ = [...new Array(n)]
_.forEach((_, idx) => {
const lowerThreshold = moment().subtract(idx, periodString as DurationInputArg2).startOf(periodString === 'months' ? 'month' : 'day')
const upperThreshold = moment().subtract(idx, periodString as DurationInputArg2).endOf(periodString === 'months' ? 'month' : 'day')
const periodTransactionAmountArray = filterLastPayments(lowerThreshold, upperThreshold, paymentList).map((p) => p.value)
const revenue = periodTransactionAmountArray.reduce((a, b) => {
return a.plus(b)
}, new Prisma.Decimal(0))
const paymentCount = periodTransactionAmountArray.length
const periodPaymentList = filterLastPayments(lowerThreshold, upperThreshold, paymentList)
const revenue = sumPaymentsValue(periodPaymentList)
const paymentCount = periodPaymentList.length
revenueArray.push(revenue)
paymentsArray.push(paymentCount)
})
Expand All @@ -43,8 +47,13 @@ const filterLastPayments = function (lowerThreshold: moment.Moment, upperThresho
})
}

const getChartData = function (n: number, periodString: string, dataArray: number[] | Prisma.Decimal[], borderColor: string, formatString = 'M/D'): ChartData {
const getChartData = function (n: number, periodString: string, dataArray: number[] | QuoteValues[], borderColor: string, formatString = 'M/D'): ChartData {
let isMultiQuote = false
if (dataArray.length > 0 && typeof dataArray[0] !== 'number') {
isMultiQuote = true
}
return {
isMultiQuote,
labels: getChartLabels(n, periodString, formatString),
datasets: [
{
Expand All @@ -60,12 +69,14 @@ const getPeriodData = function (n: number, periodString: string, paymentList: Pa
const revenue = getChartData(n, periodString, revenuePaymentData.revenue, borderColor.revenue, formatString)
const payments = getChartData(n, periodString, revenuePaymentData.payments, borderColor.payments, formatString)
const buttons = getButtonPaymentData(n, periodString, paymentList)
const totalRevenue = (revenue.datasets[0].data as QuoteValues[]).reduce(sumQuoteValues, { usd: new Prisma.Decimal(0), cad: new Prisma.Decimal(0) })
const totalPayments = (payments.datasets[0].data as any).reduce((a: number, b: number) => a + b, 0)

return {
revenue,
payments,
totalRevenue: (revenue.datasets[0].data as any).reduce((a: Prisma.Decimal, b: Prisma.Decimal) => a.plus(b), new Prisma.Decimal(0)),
totalPayments: (payments.datasets[0].data as any).reduce((a: number, b: number) => a + b, 0),
totalRevenue,
totalPayments,
buttons
}
}
Expand Down Expand Up @@ -100,14 +111,14 @@ export const getButtonPaymentData = (n: number, periodString: string, paymentLis
},
total: {
payments: 1,
revenue: new Prisma.Decimal(p.value)
revenue: p.values
}
}
buttonPaymentData[b.id] = newEntry
return
}
prevObj.total.payments += 1
prevObj.total.revenue = prevObj.total.revenue.plus(p.value)
prevObj.total.revenue = sumQuoteValues(prevObj.total.revenue, p.values)
prevObj.displayData.isXec = prevObj.displayData.isXec === true || (p.networkId === XEC_NETWORK_ID)
prevObj.displayData.isBch = prevObj.displayData.isBch === true || (p.networkId === BCH_NETWORK_ID)
const lastPayment = prevObj.displayData.lastPayment as number
Expand All @@ -116,14 +127,33 @@ export const getButtonPaymentData = (n: number, periodString: string, paymentLis
}
return buttonPaymentData
}
export const sumQuoteValues = function (a: QuoteValues, b: QuoteValues): QuoteValues {
return {
usd: (new Prisma.Decimal(a.usd)).plus(new Prisma.Decimal(b.usd)),
cad: (new Prisma.Decimal(a.cad)).plus(new Prisma.Decimal(b.cad))
}
}

export const sumPaymentsValue = function (paymentList: Payment[]): QuoteValues {
const ret: QuoteValues = {
usd: new Prisma.Decimal(0),
cad: new Prisma.Decimal(0)
}

for (const p of paymentList) {
ret.usd = ret.usd.plus(p.values.usd)
ret.cad = ret.cad.plus(p.values.cad)
}
return ret
}

export const getUserDashboardData = async function (userId: string): Promise<DashboardData> {
let dashboardData = await getCachedDashboardData(userId)
if (dashboardData === null) {
const buttons = await fetchPaybuttonArrayByUserId(userId)
const paymentList = await getPaymentList(userId)

const totalRevenue = paymentList.map((p) => p.value).reduce((a, b) => a.plus(b), new Prisma.Decimal(0))
const totalRevenue = sumPaymentsValue(paymentList)
const nMonthsTotal = getNumberOfMonths(paymentList)

const thirtyDays: PeriodData = getPeriodData(30, 'days', paymentList, { revenue: '#66fe91', payments: '#669cfe' })
Expand Down
4 changes: 2 additions & 2 deletions redis/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { AddressPaymentInfo, AddressWithTransactionsWithPrices } from 'services/
import { PaybuttonWithAddresses } from 'services/paybuttonService'
import { TransactionWithAddressAndPrices } from 'services/transactionService'
import { fetchUsersForAddress } from 'services/userService'
import { cacheBalanceFromPayments, clearBalanceCache, getBalanceForAddress, updateBalanceCacheFromTx } from './balanceCache'
import { cacheBalanceForAddress, clearBalanceCache, getBalanceForAddress, updateBalanceCacheFromTx } from './balanceCache'
import { clearDashboardCache, getUserDashboardData } from './dashboardCache'
import { appendPaybuttonToAddressesCache, cacheGroupedPayments, cacheManyTxs, generateGroupedPaymentsForAddress, getPaymentList, initPaymentCache, removePaybuttonToAddressesCache } from './paymentCache'
import { DashboardData, Payment } from './types'
Expand All @@ -20,7 +20,7 @@ export const CacheSet = {
addressCreation: async (address: AddressWithTransactionsWithPrices): Promise<void> => {
const paymentsGroupedByKey = await generateGroupedPaymentsForAddress(address)
await cacheGroupedPayments(paymentsGroupedByKey)
await cacheBalanceFromPayments(address.address, Object.values(paymentsGroupedByKey).reduce((prev, curr) => prev.concat(curr), []))
await cacheBalanceForAddress(address)
},
txCreation: async (tx: TransactionWithAddressAndPrices): Promise<void> => {
const addressString = tx.address.address
Expand Down
Loading