user stats
This commit is contained in:
		
							parent
							
								
									2c749dd07f
								
							
						
					
					
						commit
						0b3b690c10
					
				@ -20,21 +20,30 @@ export default {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    itemGrowth: async (parent, args, { models }) => {
 | 
					    itemGrowth: async (parent, args, { models }) => {
 | 
				
			||||||
      return await models.$queryRaw(
 | 
					      return await models.$queryRaw(
 | 
				
			||||||
        `SELECT date_trunc('month', created_at) AS time, count(*) as num
 | 
					        `SELECT date_trunc('month', created_at) AS time, count("parentId") as comments,
 | 
				
			||||||
 | 
					          count("subName") as jobs, count(*)-count("parentId")-count("subName") as posts
 | 
				
			||||||
        FROM "Item"
 | 
					        FROM "Item"
 | 
				
			||||||
        WHERE date_trunc('month', now_utc()) <> date_trunc('month', created_at)
 | 
					        WHERE date_trunc('month', now_utc()) <> date_trunc('month', created_at)
 | 
				
			||||||
        GROUP BY time
 | 
					        GROUP BY time
 | 
				
			||||||
        ORDER BY time ASC`)
 | 
					        ORDER BY time ASC`)
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    spentGrowth: async (parent, args, { models }) => {
 | 
					    spentGrowth: async (parent, args, { models }) => {
 | 
				
			||||||
 | 
					      // add up earn for each month
 | 
				
			||||||
 | 
					      // add up non-self votes/tips for posts and comments
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      return await models.$queryRaw(
 | 
					      return await models.$queryRaw(
 | 
				
			||||||
        `SELECT date_trunc('month', created_at) AS time, sum(sats) as num
 | 
					        `SELECT date_trunc('month', "ItemAct".created_at) AS time,
 | 
				
			||||||
 | 
					        sum(CASE WHEN act = 'STREAM' THEN sats ELSE 0 END) as jobs,
 | 
				
			||||||
 | 
					        sum(CASE WHEN act = 'VOTE' AND "Item"."userId" = "ItemAct"."userId" THEN sats ELSE 0 END) as fees,
 | 
				
			||||||
 | 
					        sum(CASE WHEN act = 'BOOST' THEN sats ELSE 0 END) as boost,
 | 
				
			||||||
 | 
					        sum(CASE WHEN act = 'TIP' THEN sats ELSE 0 END) as tips
 | 
				
			||||||
        FROM "ItemAct"
 | 
					        FROM "ItemAct"
 | 
				
			||||||
        WHERE date_trunc('month', now_utc()) <> date_trunc('month', created_at)
 | 
					        JOIN "Item" on "ItemAct"."itemId" = "Item".id
 | 
				
			||||||
 | 
					        WHERE date_trunc('month', now_utc()) <> date_trunc('month',  "ItemAct".created_at)
 | 
				
			||||||
        GROUP BY time
 | 
					        GROUP BY time
 | 
				
			||||||
        ORDER BY time ASC`)
 | 
					        ORDER BY time ASC`)
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    earnedGrowth: async (parent, args, { models }) => {
 | 
					    earnerGrowth: async (parent, args, { models }) => {
 | 
				
			||||||
      return await models.$queryRaw(
 | 
					      return await models.$queryRaw(
 | 
				
			||||||
        `SELECT time, count(distinct user_id) as num
 | 
					        `SELECT time, count(distinct user_id) as num
 | 
				
			||||||
        FROM
 | 
					        FROM
 | 
				
			||||||
@ -48,6 +57,101 @@ export default {
 | 
				
			|||||||
          WHERE date_trunc('month', now_utc()) <> date_trunc('month', created_at))) u
 | 
					          WHERE date_trunc('month', now_utc()) <> date_trunc('month', created_at))) u
 | 
				
			||||||
        GROUP BY time
 | 
					        GROUP BY time
 | 
				
			||||||
        ORDER BY time ASC`)
 | 
					        ORDER BY time ASC`)
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    stackedGrowth: async (parent, args, { models }) => {
 | 
				
			||||||
 | 
					      return await models.$queryRaw(
 | 
				
			||||||
 | 
					        `SELECT time, sum(airdrop) as airdrops, sum(post) as posts, sum(comment) as comments
 | 
				
			||||||
 | 
					        FROM
 | 
				
			||||||
 | 
					        ((SELECT date_trunc('month', "ItemAct".created_at) AS time, 0 as airdrop,
 | 
				
			||||||
 | 
					          CASE WHEN "Item"."parentId" IS NULL THEN 0 ELSE sats END as comment,
 | 
				
			||||||
 | 
					          CASE WHEN "Item"."parentId" IS NULL THEN sats ELSE 0 END as post
 | 
				
			||||||
 | 
					          FROM "ItemAct"
 | 
				
			||||||
 | 
					          JOIN "Item" on "ItemAct"."itemId" = "Item".id AND "Item"."userId" <> "ItemAct"."userId"
 | 
				
			||||||
 | 
					          WHERE date_trunc('month', now_utc()) <> date_trunc('month', "ItemAct".created_at) AND
 | 
				
			||||||
 | 
					          "ItemAct".act IN ('VOTE', 'TIP'))
 | 
				
			||||||
 | 
					        UNION ALL
 | 
				
			||||||
 | 
					        (SELECT date_trunc('month', created_at) AS time, msats / 1000 as airdrop, 0 as post, 0 as comment
 | 
				
			||||||
 | 
					          FROM "Earn"
 | 
				
			||||||
 | 
					          WHERE date_trunc('month', now_utc()) <> date_trunc('month', created_at))) u
 | 
				
			||||||
 | 
					        GROUP BY time
 | 
				
			||||||
 | 
					        ORDER BY time ASC`)
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    registrationsWeekly: async (parent, args, { models }) => {
 | 
				
			||||||
 | 
					      return await models.item.count({
 | 
				
			||||||
 | 
					        where: {
 | 
				
			||||||
 | 
					          createdAt: {
 | 
				
			||||||
 | 
					            gte: new Date(new Date().setDate(new Date().getDate() - 7))
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    activeWeekly: async (parent, args, { models }) => {
 | 
				
			||||||
 | 
					      const [{ active }] = await models.$queryRaw(
 | 
				
			||||||
 | 
					        `SELECT count(DISTINCT "userId") as active
 | 
				
			||||||
 | 
					        FROM "ItemAct"
 | 
				
			||||||
 | 
					        WHERE created_at >= now_utc() - interval '1 week'`
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					      return active
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    earnersWeekly: async (parent, args, { models }) => {
 | 
				
			||||||
 | 
					      const [{ earners }] = await models.$queryRaw(
 | 
				
			||||||
 | 
					        `SELECT count(distinct user_id) as earners
 | 
				
			||||||
 | 
					        FROM
 | 
				
			||||||
 | 
					        ((SELECT "Item"."userId" as user_id
 | 
				
			||||||
 | 
					          FROM "ItemAct"
 | 
				
			||||||
 | 
					          JOIN "Item" on "ItemAct"."itemId" = "Item".id AND "Item"."userId" <> "ItemAct"."userId"
 | 
				
			||||||
 | 
					          WHERE "ItemAct".created_at >= now_utc() - interval '1 week')
 | 
				
			||||||
 | 
					        UNION ALL
 | 
				
			||||||
 | 
					        (SELECT "userId" as user_id
 | 
				
			||||||
 | 
					          FROM "Earn"
 | 
				
			||||||
 | 
					          WHERE created_at >= now_utc() - interval '1 week')) u`)
 | 
				
			||||||
 | 
					      return earners
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    itemsWeekly: async (parent, args, { models }) => {
 | 
				
			||||||
 | 
					      const [stats] = await models.$queryRaw(
 | 
				
			||||||
 | 
					        `SELECT json_build_array(
 | 
				
			||||||
 | 
					          json_build_object('name', 'comments', 'value', count("parentId")),
 | 
				
			||||||
 | 
					          json_build_object('name', 'job', 'value', count("subName")),
 | 
				
			||||||
 | 
					          json_build_object('name', 'posts', 'value', count(*)-count("parentId")-count("subName"))) as array
 | 
				
			||||||
 | 
					        FROM "Item"
 | 
				
			||||||
 | 
					        WHERE created_at >= now_utc() - interval '1 week'`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return stats?.array
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    spentWeekly: async (parent, args, { models }) => {
 | 
				
			||||||
 | 
					      const [stats] = await models.$queryRaw(
 | 
				
			||||||
 | 
					        `SELECT json_build_array(
 | 
				
			||||||
 | 
					          json_build_object('name', 'jobs', 'value', sum(CASE WHEN act = 'STREAM' THEN sats ELSE 0 END)),
 | 
				
			||||||
 | 
					          json_build_object('name', 'fees', 'value', sum(CASE WHEN act = 'VOTE' AND "Item"."userId" = "ItemAct"."userId" THEN sats ELSE 0 END)),
 | 
				
			||||||
 | 
					          json_build_object('name', 'boost', 'value', sum(CASE WHEN act = 'BOOST' THEN sats ELSE 0 END)),
 | 
				
			||||||
 | 
					          json_build_object('name', 'tips', 'value', sum(CASE WHEN act = 'TIP' THEN sats ELSE 0 END))) as array
 | 
				
			||||||
 | 
					        FROM "ItemAct"
 | 
				
			||||||
 | 
					        JOIN "Item" on "ItemAct"."itemId" = "Item".id
 | 
				
			||||||
 | 
					        WHERE "ItemAct".created_at >= now_utc() - interval '1 week'`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return stats?.array
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    stackedWeekly: async (parent, args, { models }) => {
 | 
				
			||||||
 | 
					      const [stats] = await models.$queryRaw(
 | 
				
			||||||
 | 
					        `SELECT json_build_array(
 | 
				
			||||||
 | 
					          json_build_object('name', 'airdrops', 'value', sum(airdrop)),
 | 
				
			||||||
 | 
					          json_build_object('name', 'posts', 'value', sum(post)),
 | 
				
			||||||
 | 
					          json_build_object('name', 'comments', 'value', sum(comment))
 | 
				
			||||||
 | 
					        ) as array
 | 
				
			||||||
 | 
					        FROM
 | 
				
			||||||
 | 
					        ((SELECT 0 as airdrop,
 | 
				
			||||||
 | 
					          CASE WHEN "Item"."parentId" IS NULL THEN 0 ELSE sats END as comment,
 | 
				
			||||||
 | 
					          CASE WHEN "Item"."parentId" IS NULL THEN sats ELSE 0 END as post
 | 
				
			||||||
 | 
					          FROM "ItemAct"
 | 
				
			||||||
 | 
					          JOIN "Item" on "ItemAct"."itemId" = "Item".id AND "Item"."userId" <> "ItemAct"."userId"
 | 
				
			||||||
 | 
					          WHERE  "ItemAct".created_at >= now_utc() - interval '1 week' AND
 | 
				
			||||||
 | 
					          "ItemAct".act IN ('VOTE', 'TIP'))
 | 
				
			||||||
 | 
					        UNION ALL
 | 
				
			||||||
 | 
					        (SELECT msats / 1000 as airdrop, 0 as post, 0 as comment
 | 
				
			||||||
 | 
					          FROM "Earn"
 | 
				
			||||||
 | 
					          WHERE  created_at >= now_utc() - interval '1 week')) u`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return stats?.array
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -4,13 +4,48 @@ export default gql`
 | 
				
			|||||||
  extend type Query {
 | 
					  extend type Query {
 | 
				
			||||||
    registrationGrowth: [TimeNum!]!
 | 
					    registrationGrowth: [TimeNum!]!
 | 
				
			||||||
    activeGrowth: [TimeNum!]!
 | 
					    activeGrowth: [TimeNum!]!
 | 
				
			||||||
    itemGrowth: [TimeNum!]!
 | 
					    itemGrowth: [ItemGrowth!]!
 | 
				
			||||||
    spentGrowth: [TimeNum!]!
 | 
					    spentGrowth: [SpentGrowth!]!
 | 
				
			||||||
    earnedGrowth: [TimeNum!]!
 | 
					    stackedGrowth: [StackedGrowth!]!
 | 
				
			||||||
 | 
					    earnerGrowth: [TimeNum!]!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    registrationsWeekly: Int!
 | 
				
			||||||
 | 
					    activeWeekly: Int!
 | 
				
			||||||
 | 
					    earnersWeekly: Int!
 | 
				
			||||||
 | 
					    itemsWeekly: [NameValue!]!
 | 
				
			||||||
 | 
					    spentWeekly: [NameValue!]!
 | 
				
			||||||
 | 
					    stackedWeekly: [NameValue!]!
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  type TimeNum {
 | 
					  type TimeNum {
 | 
				
			||||||
    time: String!
 | 
					    time: String!
 | 
				
			||||||
    num: Int!
 | 
					    num: Int!
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  type NameValue {
 | 
				
			||||||
 | 
					    name: String!
 | 
				
			||||||
 | 
					    value: Int!
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  type ItemGrowth {
 | 
				
			||||||
 | 
					    time: String!
 | 
				
			||||||
 | 
					    jobs: Int!
 | 
				
			||||||
 | 
					    posts: Int!
 | 
				
			||||||
 | 
					    comments: Int!
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  type StackedGrowth {
 | 
				
			||||||
 | 
					    time: String!
 | 
				
			||||||
 | 
					    airdrops: Int!
 | 
				
			||||||
 | 
					    posts: Int!
 | 
				
			||||||
 | 
					    comments: Int!
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  type SpentGrowth {
 | 
				
			||||||
 | 
					    time: String!
 | 
				
			||||||
 | 
					    jobs: Int!
 | 
				
			||||||
 | 
					    fees: Int!
 | 
				
			||||||
 | 
					    boost: Int!
 | 
				
			||||||
 | 
					    tips: Int!
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
`
 | 
					`
 | 
				
			||||||
 | 
				
			|||||||
@ -96,9 +96,9 @@ const AnalyticsPopover = (
 | 
				
			|||||||
        visitors
 | 
					        visitors
 | 
				
			||||||
      </a>
 | 
					      </a>
 | 
				
			||||||
      <span className='mx-2 text-dark'> \ </span>
 | 
					      <span className='mx-2 text-dark'> \ </span>
 | 
				
			||||||
      <Link href='/usage' passHref>
 | 
					      <Link href='/users/forever' passHref>
 | 
				
			||||||
        <a className='text-dark d-inline-flex'>
 | 
					        <a className='text-dark d-inline-flex'>
 | 
				
			||||||
          usage
 | 
					          users
 | 
				
			||||||
        </a>
 | 
					        </a>
 | 
				
			||||||
      </Link>
 | 
					      </Link>
 | 
				
			||||||
    </Popover.Content>
 | 
					    </Popover.Content>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										35
									
								
								components/usage-header.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								components/usage-header.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					import Link from 'next/link'
 | 
				
			||||||
 | 
					import { useRouter } from 'next/router'
 | 
				
			||||||
 | 
					import { Nav, Navbar } from 'react-bootstrap'
 | 
				
			||||||
 | 
					import styles from './header.module.css'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function UsageHeader () {
 | 
				
			||||||
 | 
					  const router = useRouter()
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Navbar className='pt-0'>
 | 
				
			||||||
 | 
					      <Nav
 | 
				
			||||||
 | 
					        className={`${styles.navbarNav} justify-content-around`}
 | 
				
			||||||
 | 
					        activeKey={router.asPath}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <Nav.Item>
 | 
				
			||||||
 | 
					          <Link href='/users/week' passHref>
 | 
				
			||||||
 | 
					            <Nav.Link
 | 
				
			||||||
 | 
					              className={styles.navLink}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              week
 | 
				
			||||||
 | 
					            </Nav.Link>
 | 
				
			||||||
 | 
					          </Link>
 | 
				
			||||||
 | 
					        </Nav.Item>
 | 
				
			||||||
 | 
					        <Nav.Item>
 | 
				
			||||||
 | 
					          <Link href='/users/forever' passHref>
 | 
				
			||||||
 | 
					            <Nav.Link
 | 
				
			||||||
 | 
					              className={styles.navLink}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              forever
 | 
				
			||||||
 | 
					            </Nav.Link>
 | 
				
			||||||
 | 
					          </Link>
 | 
				
			||||||
 | 
					        </Nav.Item>
 | 
				
			||||||
 | 
					      </Nav>
 | 
				
			||||||
 | 
					    </Navbar>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,92 +0,0 @@
 | 
				
			|||||||
import { gql } from '@apollo/client'
 | 
					 | 
				
			||||||
import { getGetServerSideProps } from '../api/ssrApollo'
 | 
					 | 
				
			||||||
import Layout from '../components/layout'
 | 
					 | 
				
			||||||
import { LineChart, Line, XAxis, YAxis, Tooltip, Legend, ResponsiveContainer } from 'recharts'
 | 
					 | 
				
			||||||
import { Col, Row } from 'react-bootstrap'
 | 
					 | 
				
			||||||
import { formatSats } from '../lib/format'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const getServerSideProps = getGetServerSideProps(
 | 
					 | 
				
			||||||
  gql`
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      registrationGrowth {
 | 
					 | 
				
			||||||
        time
 | 
					 | 
				
			||||||
        num
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      activeGrowth {
 | 
					 | 
				
			||||||
        time
 | 
					 | 
				
			||||||
        num
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      itemGrowth {
 | 
					 | 
				
			||||||
        time
 | 
					 | 
				
			||||||
        num
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      spentGrowth {
 | 
					 | 
				
			||||||
        time
 | 
					 | 
				
			||||||
        num
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      earnedGrowth {
 | 
					 | 
				
			||||||
        time
 | 
					 | 
				
			||||||
        num
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }`)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const dateFormatter = timeStr => {
 | 
					 | 
				
			||||||
  const date = new Date(timeStr)
 | 
					 | 
				
			||||||
  return `${('0' + (date.getMonth() + 2)).slice(-2)}/${String(date.getFullYear()).slice(-2)}`
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default function Growth ({
 | 
					 | 
				
			||||||
  data: { registrationGrowth, activeGrowth, itemGrowth, spentGrowth, earnedGrowth }
 | 
					 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <Layout>
 | 
					 | 
				
			||||||
      <Row className='mt-3'>
 | 
					 | 
				
			||||||
        <Col>
 | 
					 | 
				
			||||||
          <GrowthLineChart data={registrationGrowth} xName='month' yName='registrations' />
 | 
					 | 
				
			||||||
        </Col>
 | 
					 | 
				
			||||||
        <Col>
 | 
					 | 
				
			||||||
          <GrowthLineChart data={activeGrowth} xName='month' yName='active users' />
 | 
					 | 
				
			||||||
        </Col>
 | 
					 | 
				
			||||||
      </Row>
 | 
					 | 
				
			||||||
      <Row className='mt-3'>
 | 
					 | 
				
			||||||
        <Col>
 | 
					 | 
				
			||||||
          <GrowthLineChart data={itemGrowth} xName='month' yName='items' />
 | 
					 | 
				
			||||||
        </Col>
 | 
					 | 
				
			||||||
        <Col>
 | 
					 | 
				
			||||||
          <GrowthLineChart data={spentGrowth} xName='month' yName='sats spent' />
 | 
					 | 
				
			||||||
        </Col>
 | 
					 | 
				
			||||||
      </Row>
 | 
					 | 
				
			||||||
      <Row className='mt-3'>
 | 
					 | 
				
			||||||
        <Col>
 | 
					 | 
				
			||||||
          <GrowthLineChart data={earnedGrowth} xName='month' yName='earning users' />
 | 
					 | 
				
			||||||
        </Col>
 | 
					 | 
				
			||||||
        <Col />
 | 
					 | 
				
			||||||
      </Row>
 | 
					 | 
				
			||||||
    </Layout>
 | 
					 | 
				
			||||||
  )
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function GrowthLineChart ({ data, xName, yName }) {
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <ResponsiveContainer width='100%' height={300} minWidth={300}>
 | 
					 | 
				
			||||||
      <LineChart
 | 
					 | 
				
			||||||
        data={data}
 | 
					 | 
				
			||||||
        margin={{
 | 
					 | 
				
			||||||
          top: 5,
 | 
					 | 
				
			||||||
          right: 5,
 | 
					 | 
				
			||||||
          left: 0,
 | 
					 | 
				
			||||||
          bottom: 0
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
      >
 | 
					 | 
				
			||||||
        <XAxis
 | 
					 | 
				
			||||||
          dataKey='time' tickFormatter={dateFormatter} name={xName}
 | 
					 | 
				
			||||||
          tick={{ fill: 'var(--theme-grey)' }}
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
        <YAxis tickFormatter={formatSats} tick={{ fill: 'var(--theme-grey)' }} />
 | 
					 | 
				
			||||||
        <Tooltip labelFormatter={dateFormatter} contentStyle={{ color: 'var(--theme-color)', backgroundColor: 'var(--theme-body)' }} />
 | 
					 | 
				
			||||||
        <Legend />
 | 
					 | 
				
			||||||
        <Line type='monotone' dataKey='num' name={yName} stroke='var(--secondary)' />
 | 
					 | 
				
			||||||
      </LineChart>
 | 
					 | 
				
			||||||
    </ResponsiveContainer>
 | 
					 | 
				
			||||||
  )
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										147
									
								
								pages/users/forever.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								pages/users/forever.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,147 @@
 | 
				
			|||||||
 | 
					import { gql } from '@apollo/client'
 | 
				
			||||||
 | 
					import { getGetServerSideProps } from '../../api/ssrApollo'
 | 
				
			||||||
 | 
					import Layout from '../../components/layout'
 | 
				
			||||||
 | 
					import { LineChart, Line, XAxis, YAxis, Tooltip, Legend, ResponsiveContainer, AreaChart, Area } from 'recharts'
 | 
				
			||||||
 | 
					import { Col, Row } from 'react-bootstrap'
 | 
				
			||||||
 | 
					import { formatSats } from '../../lib/format'
 | 
				
			||||||
 | 
					import { UsageHeader } from '../../components/usage-header'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getServerSideProps = getGetServerSideProps(
 | 
				
			||||||
 | 
					  gql`
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      registrationGrowth {
 | 
				
			||||||
 | 
					        time
 | 
				
			||||||
 | 
					        num
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      activeGrowth {
 | 
				
			||||||
 | 
					        time
 | 
				
			||||||
 | 
					        num
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      itemGrowth {
 | 
				
			||||||
 | 
					        time
 | 
				
			||||||
 | 
					        jobs
 | 
				
			||||||
 | 
					        comments
 | 
				
			||||||
 | 
					        posts
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      spentGrowth {
 | 
				
			||||||
 | 
					        time
 | 
				
			||||||
 | 
					        jobs
 | 
				
			||||||
 | 
					        fees
 | 
				
			||||||
 | 
					        boost
 | 
				
			||||||
 | 
					        tips
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      stackedGrowth {
 | 
				
			||||||
 | 
					        time
 | 
				
			||||||
 | 
					        posts
 | 
				
			||||||
 | 
					        comments
 | 
				
			||||||
 | 
					        airdrops
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      earnerGrowth {
 | 
				
			||||||
 | 
					        time
 | 
				
			||||||
 | 
					        num
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const dateFormatter = timeStr => {
 | 
				
			||||||
 | 
					  const date = new Date(timeStr)
 | 
				
			||||||
 | 
					  return `${('0' + (date.getMonth() + 2)).slice(-2)}/${String(date.getFullYear()).slice(-2)}`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function Growth ({
 | 
				
			||||||
 | 
					  data: { registrationGrowth, activeGrowth, itemGrowth, spentGrowth, earnerGrowth, stackedGrowth }
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Layout>
 | 
				
			||||||
 | 
					      <UsageHeader />
 | 
				
			||||||
 | 
					      <Row>
 | 
				
			||||||
 | 
					        <Col className='mt-3'>
 | 
				
			||||||
 | 
					          <div className='text-center text-muted font-weight-bold invisible'>earning users</div>
 | 
				
			||||||
 | 
					          <GrowthLineChart data={earnerGrowth} xName='month' yName='earning users' />
 | 
				
			||||||
 | 
					        </Col>
 | 
				
			||||||
 | 
					        <Col className='mt-3'>
 | 
				
			||||||
 | 
					          <div className='text-center text-muted font-weight-bold'>stacking</div>
 | 
				
			||||||
 | 
					          <GrowthAreaChart data={stackedGrowth} xName='month' />
 | 
				
			||||||
 | 
					        </Col>
 | 
				
			||||||
 | 
					      </Row>
 | 
				
			||||||
 | 
					      <Row>
 | 
				
			||||||
 | 
					        <Col className='mt-3'>
 | 
				
			||||||
 | 
					          <div className='text-center text-muted font-weight-bold'>items</div>
 | 
				
			||||||
 | 
					          <GrowthAreaChart data={itemGrowth} xName='month' />
 | 
				
			||||||
 | 
					        </Col>
 | 
				
			||||||
 | 
					        <Col className='mt-3'>
 | 
				
			||||||
 | 
					          <div className='text-center text-muted font-weight-bold'>spending</div>
 | 
				
			||||||
 | 
					          <GrowthAreaChart data={spentGrowth} xName='month' yName='sats spent' />
 | 
				
			||||||
 | 
					        </Col>
 | 
				
			||||||
 | 
					      </Row>
 | 
				
			||||||
 | 
					      <Row>
 | 
				
			||||||
 | 
					        <Col className='mt-3'>
 | 
				
			||||||
 | 
					          <div className='text-center text-muted font-weight-bold invisible'>registrations</div>
 | 
				
			||||||
 | 
					          <GrowthLineChart data={registrationGrowth} xName='month' yName='registrations' />
 | 
				
			||||||
 | 
					        </Col>
 | 
				
			||||||
 | 
					        <Col className='mt-3'>
 | 
				
			||||||
 | 
					          <div className='text-center text-muted font-weight-bold invisible'>active users</div>
 | 
				
			||||||
 | 
					          <GrowthLineChart data={activeGrowth} xName='month' yName='interactive users' />
 | 
				
			||||||
 | 
					        </Col>
 | 
				
			||||||
 | 
					      </Row>
 | 
				
			||||||
 | 
					    </Layout>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const COLORS = [
 | 
				
			||||||
 | 
					  'var(--secondary)',
 | 
				
			||||||
 | 
					  'var(--info)',
 | 
				
			||||||
 | 
					  'var(--success)',
 | 
				
			||||||
 | 
					  'var(--boost)',
 | 
				
			||||||
 | 
					  'var(--grey)'
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function GrowthAreaChart ({ data, xName, title }) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <ResponsiveContainer width='100%' height={300} minWidth={300}>
 | 
				
			||||||
 | 
					      <AreaChart
 | 
				
			||||||
 | 
					        data={data}
 | 
				
			||||||
 | 
					        margin={{
 | 
				
			||||||
 | 
					          top: 5,
 | 
				
			||||||
 | 
					          right: 5,
 | 
				
			||||||
 | 
					          left: 0,
 | 
				
			||||||
 | 
					          bottom: 0
 | 
				
			||||||
 | 
					        }}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <XAxis
 | 
				
			||||||
 | 
					          dataKey='time' tickFormatter={dateFormatter} name={xName}
 | 
				
			||||||
 | 
					          tick={{ fill: 'var(--theme-grey)' }}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <YAxis tickFormatter={formatSats} tick={{ fill: 'var(--theme-grey)' }} />
 | 
				
			||||||
 | 
					        <Tooltip labelFormatter={dateFormatter} contentStyle={{ color: 'var(--theme-color)', backgroundColor: 'var(--theme-body)' }} />
 | 
				
			||||||
 | 
					        <Legend />
 | 
				
			||||||
 | 
					        {Object.keys(data[0]).filter(v => v !== 'time' && v !== '__typename').map((v, i) =>
 | 
				
			||||||
 | 
					          <Area key={v} type='monotone' dataKey={v} name={v} stackId='1' stroke={COLORS[i]} fill={COLORS[i]} />)}
 | 
				
			||||||
 | 
					      </AreaChart>
 | 
				
			||||||
 | 
					    </ResponsiveContainer>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function GrowthLineChart ({ data, xName, yName }) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <ResponsiveContainer width='100%' height={300} minWidth={300}>
 | 
				
			||||||
 | 
					      <LineChart
 | 
				
			||||||
 | 
					        data={data}
 | 
				
			||||||
 | 
					        margin={{
 | 
				
			||||||
 | 
					          top: 5,
 | 
				
			||||||
 | 
					          right: 5,
 | 
				
			||||||
 | 
					          left: 0,
 | 
				
			||||||
 | 
					          bottom: 0
 | 
				
			||||||
 | 
					        }}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        <XAxis
 | 
				
			||||||
 | 
					          dataKey='time' tickFormatter={dateFormatter} name={xName}
 | 
				
			||||||
 | 
					          tick={{ fill: 'var(--theme-grey)' }}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        <YAxis tickFormatter={formatSats} tick={{ fill: 'var(--theme-grey)' }} />
 | 
				
			||||||
 | 
					        <Tooltip labelFormatter={dateFormatter} contentStyle={{ color: 'var(--theme-color)', backgroundColor: 'var(--theme-body)' }} />
 | 
				
			||||||
 | 
					        <Legend />
 | 
				
			||||||
 | 
					        <Line type='monotone' dataKey='num' name={yName} stroke='var(--secondary)' />
 | 
				
			||||||
 | 
					      </LineChart>
 | 
				
			||||||
 | 
					    </ResponsiveContainer>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										101
									
								
								pages/users/week.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								pages/users/week.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,101 @@
 | 
				
			|||||||
 | 
					import { gql } from '@apollo/client'
 | 
				
			||||||
 | 
					import { getGetServerSideProps } from '../../api/ssrApollo'
 | 
				
			||||||
 | 
					import Layout from '../../components/layout'
 | 
				
			||||||
 | 
					import { Tooltip, ResponsiveContainer, PieChart, Pie, Cell } from 'recharts'
 | 
				
			||||||
 | 
					import { Col, Row } from 'react-bootstrap'
 | 
				
			||||||
 | 
					import { UsageHeader } from '../../components/usage-header'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getServerSideProps = getGetServerSideProps(
 | 
				
			||||||
 | 
					  gql`
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      registrationsWeekly
 | 
				
			||||||
 | 
					      activeWeekly
 | 
				
			||||||
 | 
					      earnersWeekly
 | 
				
			||||||
 | 
					      itemsWeekly {
 | 
				
			||||||
 | 
					        name
 | 
				
			||||||
 | 
					        value
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      spentWeekly {
 | 
				
			||||||
 | 
					        name
 | 
				
			||||||
 | 
					        value
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      stackedWeekly {
 | 
				
			||||||
 | 
					        name
 | 
				
			||||||
 | 
					        value
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function Growth ({
 | 
				
			||||||
 | 
					  data: {
 | 
				
			||||||
 | 
					    registrationsWeekly, activeWeekly, itemsWeekly, spentWeekly,
 | 
				
			||||||
 | 
					    stackedWeekly, earnersWeekly
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Layout>
 | 
				
			||||||
 | 
					      <UsageHeader />
 | 
				
			||||||
 | 
					      <Row>
 | 
				
			||||||
 | 
					        <Col className='mt-3'>
 | 
				
			||||||
 | 
					          <div className='text-center text-muted font-weight-bold'>registrations</div>
 | 
				
			||||||
 | 
					          <h3 className='text-center'>{registrationsWeekly}</h3>
 | 
				
			||||||
 | 
					        </Col>
 | 
				
			||||||
 | 
					        <Col className='mt-3'>
 | 
				
			||||||
 | 
					          <div className='text-center text-muted font-weight-bold'>interactive users</div>
 | 
				
			||||||
 | 
					          <h3 className='text-center'>{activeWeekly}</h3>
 | 
				
			||||||
 | 
					        </Col>
 | 
				
			||||||
 | 
					        <Col className='mt-3'>
 | 
				
			||||||
 | 
					          <div className='text-center text-muted font-weight-bold'>earners</div>
 | 
				
			||||||
 | 
					          <h3 className='text-center'>{earnersWeekly}</h3>
 | 
				
			||||||
 | 
					        </Col>
 | 
				
			||||||
 | 
					      </Row>
 | 
				
			||||||
 | 
					      <Row>
 | 
				
			||||||
 | 
					        <Col className='mt-3'>
 | 
				
			||||||
 | 
					          <div className='text-center text-muted font-weight-bold'>items</div>
 | 
				
			||||||
 | 
					          <GrowthPieChart data={itemsWeekly} />
 | 
				
			||||||
 | 
					        </Col>
 | 
				
			||||||
 | 
					        <Col className='mt-3'>
 | 
				
			||||||
 | 
					          <div className='text-center text-muted font-weight-bold'>stacked</div>
 | 
				
			||||||
 | 
					          <GrowthPieChart data={stackedWeekly} />
 | 
				
			||||||
 | 
					        </Col>
 | 
				
			||||||
 | 
					        <Col className='mt-3'>
 | 
				
			||||||
 | 
					          <div className='text-center text-muted font-weight-bold'>spent</div>
 | 
				
			||||||
 | 
					          <GrowthPieChart data={spentWeekly} />
 | 
				
			||||||
 | 
					        </Col>
 | 
				
			||||||
 | 
					      </Row>
 | 
				
			||||||
 | 
					    </Layout>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const COLORS = [
 | 
				
			||||||
 | 
					  'var(--secondary)',
 | 
				
			||||||
 | 
					  'var(--info)',
 | 
				
			||||||
 | 
					  'var(--success)',
 | 
				
			||||||
 | 
					  'var(--boost)',
 | 
				
			||||||
 | 
					  'var(--grey)'
 | 
				
			||||||
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function GrowthPieChart ({ data }) {
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <ResponsiveContainer width='100%' height={250} minWidth={200}>
 | 
				
			||||||
 | 
					      <PieChart margin={{ top: 5, right: 5, bottom: 5, left: 5 }}>
 | 
				
			||||||
 | 
					        <Pie
 | 
				
			||||||
 | 
					          dataKey='value'
 | 
				
			||||||
 | 
					          isAnimationActive={false}
 | 
				
			||||||
 | 
					          data={data}
 | 
				
			||||||
 | 
					          cx='50%'
 | 
				
			||||||
 | 
					          cy='50%'
 | 
				
			||||||
 | 
					          outerRadius={80}
 | 
				
			||||||
 | 
					          fill='var(--secondary)'
 | 
				
			||||||
 | 
					          label
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          {
 | 
				
			||||||
 | 
					            data.map((entry, index) => (
 | 
				
			||||||
 | 
					              <Cell key={`cell-${index}`} fill={COLORS[index]} />
 | 
				
			||||||
 | 
					            ))
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        </Pie>
 | 
				
			||||||
 | 
					        <Tooltip />
 | 
				
			||||||
 | 
					      </PieChart>
 | 
				
			||||||
 | 
					    </ResponsiveContainer>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user