diff --git a/api/resolvers/growth.js b/api/resolvers/growth.js
index 3faa3cac..e0712766 100644
--- a/api/resolvers/growth.js
+++ b/api/resolvers/growth.js
@@ -20,21 +20,30 @@ export default {
},
itemGrowth: async (parent, args, { models }) => {
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"
WHERE date_trunc('month', now_utc()) <> date_trunc('month', created_at)
GROUP BY time
ORDER BY time ASC`)
},
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(
- `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"
- 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
ORDER BY time ASC`)
},
- earnedGrowth: async (parent, args, { models }) => {
+ earnerGrowth: async (parent, args, { models }) => {
return await models.$queryRaw(
`SELECT time, count(distinct user_id) as num
FROM
@@ -48,6 +57,101 @@ export default {
WHERE date_trunc('month', now_utc()) <> date_trunc('month', created_at))) u
GROUP BY time
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
}
}
}
diff --git a/api/typeDefs/growth.js b/api/typeDefs/growth.js
index bab68095..f3467fc7 100644
--- a/api/typeDefs/growth.js
+++ b/api/typeDefs/growth.js
@@ -4,13 +4,48 @@ export default gql`
extend type Query {
registrationGrowth: [TimeNum!]!
activeGrowth: [TimeNum!]!
- itemGrowth: [TimeNum!]!
- spentGrowth: [TimeNum!]!
- earnedGrowth: [TimeNum!]!
+ itemGrowth: [ItemGrowth!]!
+ spentGrowth: [SpentGrowth!]!
+ stackedGrowth: [StackedGrowth!]!
+ earnerGrowth: [TimeNum!]!
+
+ registrationsWeekly: Int!
+ activeWeekly: Int!
+ earnersWeekly: Int!
+ itemsWeekly: [NameValue!]!
+ spentWeekly: [NameValue!]!
+ stackedWeekly: [NameValue!]!
}
type TimeNum {
time: String!
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!
+ }
`
diff --git a/components/footer.js b/components/footer.js
index 05dfc70a..10d1e675 100644
--- a/components/footer.js
+++ b/components/footer.js
@@ -96,9 +96,9 @@ const AnalyticsPopover = (
visitors
\
-
+
- usage
+ users
diff --git a/components/usage-header.js b/components/usage-header.js
new file mode 100644
index 00000000..1d2c30ba
--- /dev/null
+++ b/components/usage-header.js
@@ -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 (
+
+
+
+ )
+}
diff --git a/pages/usage.js b/pages/usage.js
deleted file mode 100644
index 5e6f3817..00000000
--- a/pages/usage.js
+++ /dev/null
@@ -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 (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
-
-function GrowthLineChart ({ data, xName, yName }) {
- return (
-
-
-
-
-
-
-
-
-
- )
-}
diff --git a/pages/users/forever.js b/pages/users/forever.js
new file mode 100644
index 00000000..7ede0a3f
--- /dev/null
+++ b/pages/users/forever.js
@@ -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 (
+
+
+
+
+ earning users
+
+
+
+ stacking
+
+
+
+
+
+ items
+
+
+
+ spending
+
+
+
+
+
+ registrations
+
+
+
+ active users
+
+
+
+
+ )
+}
+
+const COLORS = [
+ 'var(--secondary)',
+ 'var(--info)',
+ 'var(--success)',
+ 'var(--boost)',
+ 'var(--grey)'
+]
+
+function GrowthAreaChart ({ data, xName, title }) {
+ return (
+
+
+
+
+
+
+ {Object.keys(data[0]).filter(v => v !== 'time' && v !== '__typename').map((v, i) =>
+ )}
+
+
+ )
+}
+
+function GrowthLineChart ({ data, xName, yName }) {
+ return (
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/pages/users/week.js b/pages/users/week.js
new file mode 100644
index 00000000..2f938583
--- /dev/null
+++ b/pages/users/week.js
@@ -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 (
+
+
+
+
+ registrations
+ {registrationsWeekly}
+
+
+ interactive users
+ {activeWeekly}
+
+
+ earners
+ {earnersWeekly}
+
+
+
+
+ items
+
+
+
+ stacked
+
+
+
+ spent
+
+
+
+
+ )
+}
+
+const COLORS = [
+ 'var(--secondary)',
+ 'var(--info)',
+ 'var(--success)',
+ 'var(--boost)',
+ 'var(--grey)'
+]
+
+function GrowthPieChart ({ data }) {
+ return (
+
+
+
+ {
+ data.map((entry, index) => (
+ |
+ ))
+ }
+
+
+
+
+ )
+}