Territory analytics (#1926)
* add territory to analytics selectors * implement territory analytics, revert user satistics header * fix linting errors * disallow some territory names * fix linting error * minor adjustments to header * escape input * 404 on non-existant sub * exclude unused queries depending on sub select --------- Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com> Co-authored-by: k00b <k00b@stacker.news>
This commit is contained in:
		
							parent
							
								
									5de9d92af2
								
							
						
					
					
						commit
						73170ba8a2
					
				@ -121,6 +121,39 @@ export default {
 | 
				
			|||||||
        FROM ${viewGroup(range, 'stacking_growth')}
 | 
					        FROM ${viewGroup(range, 'stacking_growth')}
 | 
				
			||||||
        GROUP BY time
 | 
					        GROUP BY time
 | 
				
			||||||
        ORDER BY time ASC`, ...range)
 | 
					        ORDER BY time ASC`, ...range)
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    itemGrowthSubs: async (parent, { when, to, from, sub }, { models }) => {
 | 
				
			||||||
 | 
					      const range = whenRange(when, from, to)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const subExists = await models.sub.findUnique({ where: { name: sub } })
 | 
				
			||||||
 | 
					      if (!subExists) throw new Error('Sub not found')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return await models.$queryRawUnsafe(`
 | 
				
			||||||
 | 
					        SELECT date_trunc('${timeUnitForRange(range)}', t) at time zone 'America/Chicago' as time, json_build_array(
 | 
				
			||||||
 | 
					          json_build_object('name', 'posts', 'value', coalesce(sum(posts),0)),
 | 
				
			||||||
 | 
					          json_build_object('name', 'comments', 'value', coalesce(sum(comments),0))
 | 
				
			||||||
 | 
					        ) AS data
 | 
				
			||||||
 | 
					        FROM ${viewGroup(range, 'sub_stats')}
 | 
				
			||||||
 | 
					        WHERE sub_name = $3
 | 
				
			||||||
 | 
					        GROUP BY time
 | 
				
			||||||
 | 
					        ORDER BY time ASC`, ...range, sub)
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    revenueGrowthSubs: async (parent, { when, to, from, sub }, { models }) => {
 | 
				
			||||||
 | 
					      const range = whenRange(when, from, to)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const subExists = await models.sub.findUnique({ where: { name: sub } })
 | 
				
			||||||
 | 
					      if (!subExists) throw new Error('Sub not found')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return await models.$queryRawUnsafe(`
 | 
				
			||||||
 | 
					        SELECT date_trunc('${timeUnitForRange(range)}', t) at time zone 'America/Chicago' as time, json_build_array(
 | 
				
			||||||
 | 
					          json_build_object('name', 'revenue', 'value', coalesce(sum(msats_revenue/1000),0)),
 | 
				
			||||||
 | 
					          json_build_object('name', 'stacking', 'value', coalesce(sum(msats_stacked/1000),0)),
 | 
				
			||||||
 | 
					          json_build_object('name', 'spending', 'value', coalesce(sum(msats_spent/1000),0))
 | 
				
			||||||
 | 
					        ) AS data
 | 
				
			||||||
 | 
					        FROM ${viewGroup(range, 'sub_stats')}
 | 
				
			||||||
 | 
					        WHERE sub_name = $3
 | 
				
			||||||
 | 
					        GROUP BY time
 | 
				
			||||||
 | 
					        ORDER BY time ASC`, ...range, sub)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -13,6 +13,8 @@ export default gql`
 | 
				
			|||||||
    spenderGrowth(when: String, from: String, to: String): [TimeData!]!
 | 
					    spenderGrowth(when: String, from: String, to: String): [TimeData!]!
 | 
				
			||||||
    stackingGrowth(when: String, from: String, to: String): [TimeData!]!
 | 
					    stackingGrowth(when: String, from: String, to: String): [TimeData!]!
 | 
				
			||||||
    stackerGrowth(when: String, from: String, to: String): [TimeData!]!
 | 
					    stackerGrowth(when: String, from: String, to: String): [TimeData!]!
 | 
				
			||||||
 | 
					    itemGrowthSubs(when: String, from: String, to: String, sub: String): [TimeData!]!
 | 
				
			||||||
 | 
					    revenueGrowthSubs(when: String, from: String, to: String, sub: String): [TimeData!]!
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  type TimeData {
 | 
					  type TimeData {
 | 
				
			||||||
 | 
				
			|||||||
@ -173,7 +173,7 @@ export default function Footer ({ links = true }) {
 | 
				
			|||||||
              <Rewards />
 | 
					              <Rewards />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div className='mb-0' style={{ fontWeight: 500 }}>
 | 
					            <div className='mb-0' style={{ fontWeight: 500 }}>
 | 
				
			||||||
              <Link href='/stackers/day' className='nav-link p-0 p-0 d-inline-flex'>
 | 
					              <Link href='/stackers/all/day' className='nav-link p-0 p-0 d-inline-flex'>
 | 
				
			||||||
                analytics
 | 
					                analytics
 | 
				
			||||||
              </Link>
 | 
					              </Link>
 | 
				
			||||||
              <span className='mx-2 text-muted'> \ </span>
 | 
					              <span className='mx-2 text-muted'> \ </span>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										79
									
								
								components/sub-analytics-header.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								components/sub-analytics-header.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,79 @@
 | 
				
			|||||||
 | 
					import { useRouter } from 'next/router'
 | 
				
			||||||
 | 
					import { Select, DatePicker } from './form'
 | 
				
			||||||
 | 
					import { useSubs } from './sub-select'
 | 
				
			||||||
 | 
					import { WHENS } from '@/lib/constants'
 | 
				
			||||||
 | 
					import { whenToFrom } from '@/lib/time'
 | 
				
			||||||
 | 
					import styles from './sub-select.module.css'
 | 
				
			||||||
 | 
					import classNames from 'classnames'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function SubAnalyticsHeader ({ pathname = null }) {
 | 
				
			||||||
 | 
					  const router = useRouter()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const path = pathname || 'stackers'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const select = async values => {
 | 
				
			||||||
 | 
					    const { sub, when, ...query } = values
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (when !== 'custom') { delete query.from; delete query.to }
 | 
				
			||||||
 | 
					    if (query.from && !query.to) return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await router.push({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      pathname: `/${path}/${sub}/${when}`,
 | 
				
			||||||
 | 
					      query
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const when = router.query.when || 'day'
 | 
				
			||||||
 | 
					  const sub = router.query.sub || 'all'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const subs = useSubs({ prependSubs: ['all'], sub, appendSubs: [], filterSubs: () => true })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className='text-muted fw-bold my-0 d-flex align-items-center flex-wrap'>
 | 
				
			||||||
 | 
					      <div className='text-muted fw-bold mb-2 d-flex align-items-center'>
 | 
				
			||||||
 | 
					        stacker analytics in
 | 
				
			||||||
 | 
					        <Select
 | 
				
			||||||
 | 
					          groupClassName='mb-0 mx-2'
 | 
				
			||||||
 | 
					          className={classNames(styles.subSelect, styles.subSelectSmall)}
 | 
				
			||||||
 | 
					          name='sub'
 | 
				
			||||||
 | 
					          size='sm'
 | 
				
			||||||
 | 
					          items={subs}
 | 
				
			||||||
 | 
					          value={sub}
 | 
				
			||||||
 | 
					          noForm
 | 
				
			||||||
 | 
					          onChange={(formik, e) => {
 | 
				
			||||||
 | 
					            const range = when === 'custom' ? { from: router.query.from, to: router.query.to } : {}
 | 
				
			||||||
 | 
					            select({ sub: e.target.value, when, ...range })
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					        for
 | 
				
			||||||
 | 
					        <Select
 | 
				
			||||||
 | 
					          groupClassName='mb-0 mx-2'
 | 
				
			||||||
 | 
					          className='w-auto'
 | 
				
			||||||
 | 
					          name='when'
 | 
				
			||||||
 | 
					          size='sm'
 | 
				
			||||||
 | 
					          items={WHENS}
 | 
				
			||||||
 | 
					          value={when}
 | 
				
			||||||
 | 
					          noForm
 | 
				
			||||||
 | 
					          onChange={(formik, e) => {
 | 
				
			||||||
 | 
					            const range = e.target.value === 'custom' ? { from: whenToFrom(when), to: Date.now() } : {}
 | 
				
			||||||
 | 
					            select({ sub, when: e.target.value, ...range })
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      {when === 'custom' &&
 | 
				
			||||||
 | 
					        <DatePicker
 | 
				
			||||||
 | 
					          noForm
 | 
				
			||||||
 | 
					          fromName='from'
 | 
				
			||||||
 | 
					          toName='to'
 | 
				
			||||||
 | 
					          className='p-0 px-2 mb-0'
 | 
				
			||||||
 | 
					          onChange={(formik, [from, to], e) => {
 | 
				
			||||||
 | 
					            select({ sub, when, from: from.getTime(), to: to.getTime() })
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					          from={router.query.from}
 | 
				
			||||||
 | 
					          to={router.query.to}
 | 
				
			||||||
 | 
					          when={when}
 | 
				
			||||||
 | 
					        />}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -3,10 +3,10 @@ import { Select, DatePicker } from './form'
 | 
				
			|||||||
import { WHENS } from '@/lib/constants'
 | 
					import { WHENS } from '@/lib/constants'
 | 
				
			||||||
import { whenToFrom } from '@/lib/time'
 | 
					import { whenToFrom } from '@/lib/time'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function UsageHeader ({ pathname = null }) {
 | 
					export function UserAnalyticsHeader ({ pathname = null }) {
 | 
				
			||||||
  const router = useRouter()
 | 
					  const router = useRouter()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const path = pathname || 'stackers'
 | 
					  const path = pathname || 'satistics/graph'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const select = async values => {
 | 
					  const select = async values => {
 | 
				
			||||||
    const { when, ...query } = values
 | 
					    const { when, ...query } = values
 | 
				
			||||||
@ -2,6 +2,7 @@
 | 
				
			|||||||
// to be loaded from the server
 | 
					// to be loaded from the server
 | 
				
			||||||
export const DEFAULT_SUBS = ['bitcoin', 'nostr', 'tech', 'meta', 'jobs']
 | 
					export const DEFAULT_SUBS = ['bitcoin', 'nostr', 'tech', 'meta', 'jobs']
 | 
				
			||||||
export const DEFAULT_SUBS_NO_JOBS = DEFAULT_SUBS.filter(s => s !== 'jobs')
 | 
					export const DEFAULT_SUBS_NO_JOBS = DEFAULT_SUBS.filter(s => s !== 'jobs')
 | 
				
			||||||
 | 
					export const RESERVED_SUB_NAMES = ['all', 'home']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const PAID_ACTION_PAYMENT_METHODS = {
 | 
					export const PAID_ACTION_PAYMENT_METHODS = {
 | 
				
			||||||
  FEE_CREDIT: 'FEE_CREDIT',
 | 
					  FEE_CREDIT: 'FEE_CREDIT',
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,8 @@ import { string, ValidationError, number, object, array, boolean, date } from '.
 | 
				
			|||||||
import {
 | 
					import {
 | 
				
			||||||
  BOOST_MIN, MAX_POLL_CHOICE_LENGTH, MAX_TITLE_LENGTH, MAX_POLL_NUM_CHOICES,
 | 
					  BOOST_MIN, MAX_POLL_CHOICE_LENGTH, MAX_TITLE_LENGTH, MAX_POLL_NUM_CHOICES,
 | 
				
			||||||
  MIN_POLL_NUM_CHOICES, MAX_FORWARDS, BOOST_MULT, MAX_TERRITORY_DESC_LENGTH, POST_TYPES,
 | 
					  MIN_POLL_NUM_CHOICES, MAX_FORWARDS, BOOST_MULT, MAX_TERRITORY_DESC_LENGTH, POST_TYPES,
 | 
				
			||||||
  TERRITORY_BILLING_TYPES, MAX_COMMENT_TEXT_LENGTH, MAX_POST_TEXT_LENGTH, MIN_TITLE_LENGTH, BOUNTY_MIN, BOUNTY_MAX
 | 
					  TERRITORY_BILLING_TYPES, MAX_COMMENT_TEXT_LENGTH, MAX_POST_TEXT_LENGTH, MIN_TITLE_LENGTH, BOUNTY_MIN, BOUNTY_MAX,
 | 
				
			||||||
 | 
					  RESERVED_SUB_NAMES
 | 
				
			||||||
} from './constants'
 | 
					} from './constants'
 | 
				
			||||||
import { SUPPORTED_CURRENCIES } from './currency'
 | 
					import { SUPPORTED_CURRENCIES } from './currency'
 | 
				
			||||||
import { NOSTR_MAX_RELAY_NUM, NOSTR_PUBKEY_BECH32, NOSTR_PUBKEY_HEX } from './nostr'
 | 
					import { NOSTR_MAX_RELAY_NUM, NOSTR_PUBKEY_BECH32, NOSTR_PUBKEY_HEX } from './nostr'
 | 
				
			||||||
@ -306,7 +307,7 @@ export function territorySchema (args) {
 | 
				
			|||||||
          const isArchived = sub => sub.status === 'STOPPED'
 | 
					          const isArchived = sub => sub.status === 'STOPPED'
 | 
				
			||||||
          const filter = sub => editing ? !isEdit(sub) : !isArchived(sub)
 | 
					          const filter = sub => editing ? !isEdit(sub) : !isArchived(sub)
 | 
				
			||||||
          const exists = await subExists(name, { ...args, filter })
 | 
					          const exists = await subExists(name, { ...args, filter })
 | 
				
			||||||
          return !exists
 | 
					          return !exists & !RESERVED_SUB_NAMES.includes(name)
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        message: 'taken'
 | 
					        message: 'taken'
 | 
				
			||||||
      }),
 | 
					      }),
 | 
				
			||||||
 | 
				
			|||||||
@ -6,7 +6,7 @@ import { useRouter } from 'next/router'
 | 
				
			|||||||
import PageLoading from '@/components/page-loading'
 | 
					import PageLoading from '@/components/page-loading'
 | 
				
			||||||
import dynamic from 'next/dynamic'
 | 
					import dynamic from 'next/dynamic'
 | 
				
			||||||
import { numWithUnits } from '@/lib/format'
 | 
					import { numWithUnits } from '@/lib/format'
 | 
				
			||||||
import { UsageHeader } from '@/components/usage-header'
 | 
					import { UserAnalyticsHeader } from '@/components/user-analytics-header'
 | 
				
			||||||
import { SatisticsHeader } from '..'
 | 
					import { SatisticsHeader } from '..'
 | 
				
			||||||
import { WhenComposedChartSkeleton, WhenAreaChartSkeleton } from '@/components/charts-skeletons'
 | 
					import { WhenComposedChartSkeleton, WhenAreaChartSkeleton } from '@/components/charts-skeletons'
 | 
				
			||||||
import OverlayTrigger from 'react-bootstrap/OverlayTrigger'
 | 
					import OverlayTrigger from 'react-bootstrap/OverlayTrigger'
 | 
				
			||||||
@ -55,7 +55,7 @@ export default function Satistics ({ ssrData }) {
 | 
				
			|||||||
        <SatisticsHeader />
 | 
					        <SatisticsHeader />
 | 
				
			||||||
        <div className='tab-content' id='myTabContent'>
 | 
					        <div className='tab-content' id='myTabContent'>
 | 
				
			||||||
          <div className='tab-pane fade show active text-muted' id='statistics' role='tabpanel' aria-labelledby='statistics-tab'>
 | 
					          <div className='tab-pane fade show active text-muted' id='statistics' role='tabpanel' aria-labelledby='statistics-tab'>
 | 
				
			||||||
            <UsageHeader pathname='satistics/graphs' />
 | 
					            <UserAnalyticsHeader pathname='satistics/graphs' />
 | 
				
			||||||
            <div className='mt-3'>
 | 
					            <div className='mt-3'>
 | 
				
			||||||
              <div className='d-flex row justify-content-between'>
 | 
					              <div className='d-flex row justify-content-between'>
 | 
				
			||||||
                <div className='col-md-6 mb-2'>
 | 
					                <div className='col-md-6 mb-2'>
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										157
									
								
								pages/stackers/[sub]/[when].js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								pages/stackers/[sub]/[when].js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,157 @@
 | 
				
			|||||||
 | 
					import { gql, useQuery } from '@apollo/client'
 | 
				
			||||||
 | 
					import { getGetServerSideProps } from '@/api/ssrApollo'
 | 
				
			||||||
 | 
					import Layout from '@/components/layout'
 | 
				
			||||||
 | 
					import Col from 'react-bootstrap/Col'
 | 
				
			||||||
 | 
					import Row from 'react-bootstrap/Row'
 | 
				
			||||||
 | 
					import { SubAnalyticsHeader } from '@/components/sub-analytics-header'
 | 
				
			||||||
 | 
					import { useRouter } from 'next/router'
 | 
				
			||||||
 | 
					import dynamic from 'next/dynamic'
 | 
				
			||||||
 | 
					import PageLoading from '@/components/page-loading'
 | 
				
			||||||
 | 
					import { WhenAreaChartSkeleton, WhenComposedChartSkeleton, WhenLineChartSkeleton } from '@/components/charts-skeletons'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const WhenAreaChart = dynamic(() => import('@/components/charts').then(mod => mod.WhenAreaChart), {
 | 
				
			||||||
 | 
					  loading: () => <WhenAreaChartSkeleton />
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					const WhenLineChart = dynamic(() => import('@/components/charts').then(mod => mod.WhenLineChart), {
 | 
				
			||||||
 | 
					  loading: () => <WhenLineChartSkeleton />
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					const WhenComposedChart = dynamic(() => import('@/components/charts').then(mod => mod.WhenComposedChart), {
 | 
				
			||||||
 | 
					  loading: () => <WhenComposedChartSkeleton />
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const GROWTH_QUERY = gql`
 | 
				
			||||||
 | 
					  query Growth($when: String!, $from: String, $to: String, $sub: String, $subSelect: Boolean = false)
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    registrationGrowth(when: $when, from: $from, to: $to) @skip(if: $subSelect) {
 | 
				
			||||||
 | 
					      time
 | 
				
			||||||
 | 
					      data {
 | 
				
			||||||
 | 
					        name
 | 
				
			||||||
 | 
					        value
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    itemGrowth(when: $when, from: $from, to: $to) @skip(if: $subSelect) {
 | 
				
			||||||
 | 
					      time
 | 
				
			||||||
 | 
					      data {
 | 
				
			||||||
 | 
					        name
 | 
				
			||||||
 | 
					        value
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    spendingGrowth(when: $when, from: $from, to: $to) @skip(if: $subSelect) {
 | 
				
			||||||
 | 
					      time
 | 
				
			||||||
 | 
					      data {
 | 
				
			||||||
 | 
					        name
 | 
				
			||||||
 | 
					        value
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    spenderGrowth(when: $when, from: $from, to: $to) @skip(if: $subSelect) {
 | 
				
			||||||
 | 
					      time
 | 
				
			||||||
 | 
					      data {
 | 
				
			||||||
 | 
					        name
 | 
				
			||||||
 | 
					        value
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    stackingGrowth(when: $when, from: $from, to: $to) @skip(if: $subSelect) {
 | 
				
			||||||
 | 
					      time
 | 
				
			||||||
 | 
					      data {
 | 
				
			||||||
 | 
					        name
 | 
				
			||||||
 | 
					        value
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    stackerGrowth(when: $when, from: $from, to: $to) @skip(if: $subSelect) {
 | 
				
			||||||
 | 
					      time
 | 
				
			||||||
 | 
					      data {
 | 
				
			||||||
 | 
					        name
 | 
				
			||||||
 | 
					        value
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    itemGrowthSubs(when: $when, from: $from, to: $to, sub: $sub) @include(if: $subSelect) {
 | 
				
			||||||
 | 
					      time
 | 
				
			||||||
 | 
					      data {
 | 
				
			||||||
 | 
					        name
 | 
				
			||||||
 | 
					        value
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    revenueGrowthSubs(when: $when, from: $from, to: $to, sub: $sub) @include(if: $subSelect) {
 | 
				
			||||||
 | 
					      time
 | 
				
			||||||
 | 
					      data {
 | 
				
			||||||
 | 
					        name
 | 
				
			||||||
 | 
					        value
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const variablesFunc = vars => ({ ...vars, subSelect: vars.sub !== 'all' })
 | 
				
			||||||
 | 
					export const getServerSideProps = getGetServerSideProps({ query: GROWTH_QUERY, variables: variablesFunc })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function Growth ({ ssrData }) {
 | 
				
			||||||
 | 
					  const router = useRouter()
 | 
				
			||||||
 | 
					  const { when, from, to, sub } = router.query
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { data } = useQuery(GROWTH_QUERY, { variables: { when, from, to, sub, subSelect: sub !== 'all' } })
 | 
				
			||||||
 | 
					  if (!data && !ssrData) return <PageLoading />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const {
 | 
				
			||||||
 | 
					    registrationGrowth,
 | 
				
			||||||
 | 
					    itemGrowth,
 | 
				
			||||||
 | 
					    spendingGrowth,
 | 
				
			||||||
 | 
					    spenderGrowth,
 | 
				
			||||||
 | 
					    stackingGrowth,
 | 
				
			||||||
 | 
					    stackerGrowth,
 | 
				
			||||||
 | 
					    itemGrowthSubs,
 | 
				
			||||||
 | 
					    revenueGrowthSubs
 | 
				
			||||||
 | 
					  } = data || ssrData
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (sub === 'all') {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <Layout>
 | 
				
			||||||
 | 
					        <SubAnalyticsHeader />
 | 
				
			||||||
 | 
					        <Row>
 | 
				
			||||||
 | 
					          <Col className='mt-3'>
 | 
				
			||||||
 | 
					            <div className='text-center text-muted fw-bold'>stackers</div>
 | 
				
			||||||
 | 
					            <WhenLineChart data={stackerGrowth} />
 | 
				
			||||||
 | 
					          </Col>
 | 
				
			||||||
 | 
					          <Col className='mt-3'>
 | 
				
			||||||
 | 
					            <div className='text-center text-muted fw-bold'>stacking</div>
 | 
				
			||||||
 | 
					            <WhenAreaChart data={stackingGrowth} />
 | 
				
			||||||
 | 
					          </Col>
 | 
				
			||||||
 | 
					        </Row>
 | 
				
			||||||
 | 
					        <Row>
 | 
				
			||||||
 | 
					          <Col className='mt-3'>
 | 
				
			||||||
 | 
					            <div className='text-center text-muted fw-bold'>spenders</div>
 | 
				
			||||||
 | 
					            <WhenLineChart data={spenderGrowth} />
 | 
				
			||||||
 | 
					          </Col>
 | 
				
			||||||
 | 
					          <Col className='mt-3'>
 | 
				
			||||||
 | 
					            <div className='text-center text-muted fw-bold'>spending</div>
 | 
				
			||||||
 | 
					            <WhenAreaChart data={spendingGrowth} />
 | 
				
			||||||
 | 
					          </Col>
 | 
				
			||||||
 | 
					        </Row>
 | 
				
			||||||
 | 
					        <Row>
 | 
				
			||||||
 | 
					          <Col className='mt-3'>
 | 
				
			||||||
 | 
					            <div className='text-center text-muted fw-bold'>registrations</div>
 | 
				
			||||||
 | 
					            <WhenAreaChart data={registrationGrowth} />
 | 
				
			||||||
 | 
					          </Col>
 | 
				
			||||||
 | 
					          <Col className='mt-3'>
 | 
				
			||||||
 | 
					            <div className='text-center text-muted fw-bold'>items</div>
 | 
				
			||||||
 | 
					            <WhenComposedChart data={itemGrowth} areaNames={['posts', 'comments', 'jobs']} areaAxis='left' lineNames={['comments/posts', 'territories']} lineAxis='right' barNames={['zaps']} />
 | 
				
			||||||
 | 
					          </Col>
 | 
				
			||||||
 | 
					        </Row>
 | 
				
			||||||
 | 
					      </Layout>
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <Layout>
 | 
				
			||||||
 | 
					        <SubAnalyticsHeader />
 | 
				
			||||||
 | 
					        <Row>
 | 
				
			||||||
 | 
					          <Col className='mt-3'>
 | 
				
			||||||
 | 
					            <div className='text-center text-muted fw-bold'>items</div>
 | 
				
			||||||
 | 
					            <WhenLineChart data={itemGrowthSubs} />
 | 
				
			||||||
 | 
					          </Col>
 | 
				
			||||||
 | 
					          <Col className='mt-3'>
 | 
				
			||||||
 | 
					            <div className='text-center text-muted fw-bold'>sats</div>
 | 
				
			||||||
 | 
					            <WhenLineChart data={revenueGrowthSubs} />
 | 
				
			||||||
 | 
					          </Col>
 | 
				
			||||||
 | 
					        </Row>
 | 
				
			||||||
 | 
					      </Layout>
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,115 +0,0 @@
 | 
				
			|||||||
import { gql, useQuery } from '@apollo/client'
 | 
					 | 
				
			||||||
import { getGetServerSideProps } from '@/api/ssrApollo'
 | 
					 | 
				
			||||||
import Layout from '@/components/layout'
 | 
					 | 
				
			||||||
import Col from 'react-bootstrap/Col'
 | 
					 | 
				
			||||||
import Row from 'react-bootstrap/Row'
 | 
					 | 
				
			||||||
import { UsageHeader } from '@/components/usage-header'
 | 
					 | 
				
			||||||
import { useRouter } from 'next/router'
 | 
					 | 
				
			||||||
import dynamic from 'next/dynamic'
 | 
					 | 
				
			||||||
import PageLoading from '@/components/page-loading'
 | 
					 | 
				
			||||||
import { WhenAreaChartSkeleton, WhenComposedChartSkeleton, WhenLineChartSkeleton } from '@/components/charts-skeletons'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const WhenAreaChart = dynamic(() => import('@/components/charts').then(mod => mod.WhenAreaChart), {
 | 
					 | 
				
			||||||
  loading: () => <WhenAreaChartSkeleton />
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
const WhenLineChart = dynamic(() => import('@/components/charts').then(mod => mod.WhenLineChart), {
 | 
					 | 
				
			||||||
  loading: () => <WhenLineChartSkeleton />
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
const WhenComposedChart = dynamic(() => import('@/components/charts').then(mod => mod.WhenComposedChart), {
 | 
					 | 
				
			||||||
  loading: () => <WhenComposedChartSkeleton />
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const GROWTH_QUERY = gql`
 | 
					 | 
				
			||||||
  query Growth($when: String!, $from: String, $to: String)
 | 
					 | 
				
			||||||
  {
 | 
					 | 
				
			||||||
    registrationGrowth(when: $when, from: $from, to: $to) {
 | 
					 | 
				
			||||||
      time
 | 
					 | 
				
			||||||
      data {
 | 
					 | 
				
			||||||
        name
 | 
					 | 
				
			||||||
        value
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    itemGrowth(when: $when, from: $from, to: $to) {
 | 
					 | 
				
			||||||
      time
 | 
					 | 
				
			||||||
      data {
 | 
					 | 
				
			||||||
        name
 | 
					 | 
				
			||||||
        value
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    spendingGrowth(when: $when, from: $from, to: $to) {
 | 
					 | 
				
			||||||
      time
 | 
					 | 
				
			||||||
      data {
 | 
					 | 
				
			||||||
        name
 | 
					 | 
				
			||||||
        value
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    spenderGrowth(when: $when, from: $from, to: $to) {
 | 
					 | 
				
			||||||
      time
 | 
					 | 
				
			||||||
      data {
 | 
					 | 
				
			||||||
        name
 | 
					 | 
				
			||||||
        value
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    stackingGrowth(when: $when, from: $from, to: $to) {
 | 
					 | 
				
			||||||
      time
 | 
					 | 
				
			||||||
      data {
 | 
					 | 
				
			||||||
        name
 | 
					 | 
				
			||||||
        value
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    stackerGrowth(when: $when, from: $from, to: $to) {
 | 
					 | 
				
			||||||
      time
 | 
					 | 
				
			||||||
      data {
 | 
					 | 
				
			||||||
        name
 | 
					 | 
				
			||||||
        value
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const getServerSideProps = getGetServerSideProps({ query: GROWTH_QUERY })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default function Growth ({ ssrData }) {
 | 
					 | 
				
			||||||
  const router = useRouter()
 | 
					 | 
				
			||||||
  const { when, from, to } = router.query
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const { data } = useQuery(GROWTH_QUERY, { variables: { when, from, to } })
 | 
					 | 
				
			||||||
  if (!data && !ssrData) return <PageLoading />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const { registrationGrowth, itemGrowth, spendingGrowth, spenderGrowth, stackingGrowth, stackerGrowth } = data || ssrData
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <Layout>
 | 
					 | 
				
			||||||
      <UsageHeader />
 | 
					 | 
				
			||||||
      <Row>
 | 
					 | 
				
			||||||
        <Col className='mt-3'>
 | 
					 | 
				
			||||||
          <div className='text-center text-muted fw-bold'>stackers</div>
 | 
					 | 
				
			||||||
          <WhenLineChart data={stackerGrowth} />
 | 
					 | 
				
			||||||
        </Col>
 | 
					 | 
				
			||||||
        <Col className='mt-3'>
 | 
					 | 
				
			||||||
          <div className='text-center text-muted fw-bold'>stacking</div>
 | 
					 | 
				
			||||||
          <WhenAreaChart data={stackingGrowth} />
 | 
					 | 
				
			||||||
        </Col>
 | 
					 | 
				
			||||||
      </Row>
 | 
					 | 
				
			||||||
      <Row>
 | 
					 | 
				
			||||||
        <Col className='mt-3'>
 | 
					 | 
				
			||||||
          <div className='text-center text-muted fw-bold'>spenders</div>
 | 
					 | 
				
			||||||
          <WhenLineChart data={spenderGrowth} />
 | 
					 | 
				
			||||||
        </Col>
 | 
					 | 
				
			||||||
        <Col className='mt-3'>
 | 
					 | 
				
			||||||
          <div className='text-center text-muted fw-bold'>spending</div>
 | 
					 | 
				
			||||||
          <WhenAreaChart data={spendingGrowth} />
 | 
					 | 
				
			||||||
        </Col>
 | 
					 | 
				
			||||||
      </Row>
 | 
					 | 
				
			||||||
      <Row>
 | 
					 | 
				
			||||||
        <Col className='mt-3'>
 | 
					 | 
				
			||||||
          <div className='text-center text-muted fw-bold'>registrations</div>
 | 
					 | 
				
			||||||
          <WhenAreaChart data={registrationGrowth} />
 | 
					 | 
				
			||||||
        </Col>
 | 
					 | 
				
			||||||
        <Col className='mt-3'>
 | 
					 | 
				
			||||||
          <div className='text-center text-muted fw-bold'>items</div>
 | 
					 | 
				
			||||||
          <WhenComposedChart data={itemGrowth} areaNames={['posts', 'comments', 'jobs']} areaAxis='left' lineNames={['comments/posts', 'territories']} lineAxis='right' barNames={['zaps']} />
 | 
					 | 
				
			||||||
        </Col>
 | 
					 | 
				
			||||||
      </Row>
 | 
					 | 
				
			||||||
    </Layout>
 | 
					 | 
				
			||||||
  )
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user