Show founded territories on profile (#868)

* add nterritories field to User

* add userSubs query

* show territories tab on user profiles

hide the tab if user has 0 territories, except when the
viewer navigated directly to the user's territories page

* add USER_WITH_SUBS query for user territories page

* add user territories page
This commit is contained in:
mzivil 2024-02-21 20:55:48 -05:00 committed by GitHub
parent 6f8b6c36d8
commit b0bf7add34
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 140 additions and 3 deletions

View File

@ -151,6 +151,49 @@ export default {
OFFSET $3
LIMIT $4`, ...range, decodedCursor.offset, limit)
return {
cursor: subs.length === limit ? nextCursorEncoded(decodedCursor, limit) : null,
subs
}
},
userSubs: async (_parent, { name, cursor, when, by, from, to, limit = LIMIT }, { models }) => {
if (!name) {
throw new GraphQLError('must supply user name', { extensions: { code: 'BAD_INPUT' } })
}
const user = await models.user.findUnique({ where: { name } })
if (!user) {
throw new GraphQLError('no user has that name', { extensions: { code: 'BAD_INPUT' } })
}
const decodedCursor = decodeCursor(cursor)
const range = whenRange(when, from, to || decodeCursor.time)
let column
switch (by) {
case 'revenue': column = 'revenue'; break
case 'spent': column = 'spent'; break
case 'posts': column = 'nposts'; break
case 'comments': column = 'ncomments'; break
default: column = 'stacked'; break
}
const subs = await models.$queryRawUnsafe(`
SELECT "Sub".*,
COALESCE(floor(sum(msats_revenue)/1000), 0) as revenue,
COALESCE(floor(sum(msats_stacked)/1000), 0) as stacked,
COALESCE(floor(sum(msats_spent)/1000), 0) as spent,
COALESCE(sum(posts), 0) as nposts,
COALESCE(sum(comments), 0) as ncomments
FROM ${subViewGroup(range)} ss
JOIN "Sub" on "Sub".name = ss.sub_name
WHERE "Sub"."userId" = $3
AND "Sub".status = 'ACTIVE'
GROUP BY "Sub".name
ORDER BY ${column} DESC NULLS LAST, "Sub".created_at ASC
OFFSET $4
LIMIT $5`, ...range, user.id, decodedCursor.offset, limit)
return {
cursor: subs.length === limit ? nextCursorEncoded(decodedCursor, limit) : null,
subs

View File

@ -698,6 +698,23 @@ export default {
}
})
},
nterritories: async (user, { when, from, to }, { models }) => {
if (typeof user.nterritories !== 'undefined') {
return user.nterritories
}
const [gte, lte] = whenRange(when, from, to)
return await models.sub.count({
where: {
userId: user.id,
status: 'ACTIVE',
createdAt: {
gte,
lte
}
}
})
},
bio: async (user, args, { models, me }) => {
return getItem(user, { id: user.bioId }, { models, me })
}

View File

@ -6,6 +6,7 @@ export default gql`
subLatestPost(name: String!): String
subs: [Sub!]!
topSubs(cursor: String, when: String, from: String, to: String, by: String, limit: Limit): Subs
userSubs(name: String!, cursor: String, when: String, from: String, to: String, by: String, limit: Limit): Subs
}
type Subs {

View File

@ -39,6 +39,7 @@ export default gql`
name: String
nitems(when: String, from: String, to: String): Int!
nposts(when: String, from: String, to: String): Int!
nterritories(when: String, from: String, to: String): Int!
ncomments(when: String, from: String, to: String): Int!
bio: Item
bioId: Int

View File

@ -32,21 +32,25 @@ import TwitterIcon from '../svgs/twitter-fill.svg'
export default function UserHeader ({ user }) {
const router = useRouter()
const pathParts = router.asPath.split('/')
const activeKey = pathParts[2] === 'territories' ? 'territories' : pathParts.length === 2 ? 'bio' : 'items'
const showTerritoriesTab = activeKey === 'territories' || user.nterritories > 0
return (
<>
<HeaderHeader user={user} />
<Nav
className={styles.nav}
activeKey={!!router.asPath.split('/')[2]}
activeKey={activeKey}
>
<Nav.Item>
<Link href={'/' + user.name} passHref legacyBehavior>
<Nav.Link eventKey={false}>bio</Nav.Link>
<Nav.Link eventKey='bio'>bio</Nav.Link>
</Link>
</Nav.Item>
<Nav.Item>
<Link href={'/' + user.name + '/all'} passHref legacyBehavior>
<Nav.Link eventKey>
<Nav.Link eventKey='items'>
{numWithUnits(user.nitems, {
abbreviate: false,
unitSingular: 'item',
@ -55,6 +59,19 @@ export default function UserHeader ({ user }) {
</Nav.Link>
</Link>
</Nav.Item>
{showTerritoriesTab && (
<Nav.Item>
<Link href={'/' + user.name + '/territories'} passHref legacyBehavior>
<Nav.Link eventKey='territories'>
{numWithUnits(user.nterritories, {
abbreviate: false,
unitSingular: 'territory',
unitPlural: 'territories'
})}
</Nav.Link>
</Link>
</Nav.Item>
)}
</Nav>
</>
)

View File

@ -1,6 +1,7 @@
import { gql } from '@apollo/client'
import { COMMENTS, COMMENTS_ITEM_EXT_FIELDS } from './comments'
import { ITEM_FIELDS, ITEM_FULL_FIELDS } from './items'
import { SUB_FULL_FIELDS } from './subs'
export const ME = gql`
{
@ -184,6 +185,7 @@ export const USER_FIELDS = gql`
since
photoId
nitems
nterritories
meSubscriptionPosts
meSubscriptionComments
meMute
@ -283,3 +285,26 @@ export const USER_WITH_ITEMS = gql`
}
}
}`
export const USER_WITH_SUBS = gql`
${USER_FIELDS}
${SUB_FULL_FIELDS}
query UserWithSubs($name: String!, $cursor: String, $type: String, $when: String, $from: String, $to: String, $by: String) {
user(name: $name) {
...UserFields
}
userSubs(name: $name, cursor: $cursor) {
cursor
subs {
...SubFullFields
ncomments(when: "forever")
nposts(when: "forever")
optional {
stacked(when: "forever")
spent(when: "forever")
revenue(when: "forever")
}
}
}
}`

View File

@ -0,0 +1,33 @@
import { getGetServerSideProps } from '../../api/ssrApollo'
import { useRouter } from 'next/router'
import { USER, USER_WITH_SUBS } from '../../fragments/users'
import { useQuery } from '@apollo/client'
import PageLoading from '../../components/page-loading'
import { UserLayout } from '.'
import TerritoryList from '../../components/territory-list'
export const getServerSideProps = getGetServerSideProps({ query: USER_WITH_SUBS })
export default function UserTerritories ({ ssrData }) {
const router = useRouter()
const variables = { ...router.query }
const { data } = useQuery(USER, { variables })
if (!data && !ssrData) return <PageLoading />
const { user } = data || ssrData
return (
<UserLayout user={user}>
<div className='mt-2'>
<TerritoryList
ssrData={ssrData}
query={USER_WITH_SUBS}
variables={variables}
destructureData={data => data.userSubs}
rank
/>
</div>
</UserLayout>
)
}