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:
parent
6f8b6c36d8
commit
b0bf7add34
|
@ -151,6 +151,49 @@ export default {
|
||||||
OFFSET $3
|
OFFSET $3
|
||||||
LIMIT $4`, ...range, decodedCursor.offset, limit)
|
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 {
|
return {
|
||||||
cursor: subs.length === limit ? nextCursorEncoded(decodedCursor, limit) : null,
|
cursor: subs.length === limit ? nextCursorEncoded(decodedCursor, limit) : null,
|
||||||
subs
|
subs
|
||||||
|
|
|
@ -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 }) => {
|
bio: async (user, args, { models, me }) => {
|
||||||
return getItem(user, { id: user.bioId }, { models, me })
|
return getItem(user, { id: user.bioId }, { models, me })
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ export default gql`
|
||||||
subLatestPost(name: String!): String
|
subLatestPost(name: String!): String
|
||||||
subs: [Sub!]!
|
subs: [Sub!]!
|
||||||
topSubs(cursor: String, when: String, from: String, to: String, by: String, limit: Limit): Subs
|
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 {
|
type Subs {
|
||||||
|
|
|
@ -39,6 +39,7 @@ export default gql`
|
||||||
name: String
|
name: String
|
||||||
nitems(when: String, from: String, to: String): Int!
|
nitems(when: String, from: String, to: String): Int!
|
||||||
nposts(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!
|
ncomments(when: String, from: String, to: String): Int!
|
||||||
bio: Item
|
bio: Item
|
||||||
bioId: Int
|
bioId: Int
|
||||||
|
|
|
@ -32,21 +32,25 @@ import TwitterIcon from '../svgs/twitter-fill.svg'
|
||||||
export default function UserHeader ({ user }) {
|
export default function UserHeader ({ user }) {
|
||||||
const router = useRouter()
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<HeaderHeader user={user} />
|
<HeaderHeader user={user} />
|
||||||
<Nav
|
<Nav
|
||||||
className={styles.nav}
|
className={styles.nav}
|
||||||
activeKey={!!router.asPath.split('/')[2]}
|
activeKey={activeKey}
|
||||||
>
|
>
|
||||||
<Nav.Item>
|
<Nav.Item>
|
||||||
<Link href={'/' + user.name} passHref legacyBehavior>
|
<Link href={'/' + user.name} passHref legacyBehavior>
|
||||||
<Nav.Link eventKey={false}>bio</Nav.Link>
|
<Nav.Link eventKey='bio'>bio</Nav.Link>
|
||||||
</Link>
|
</Link>
|
||||||
</Nav.Item>
|
</Nav.Item>
|
||||||
<Nav.Item>
|
<Nav.Item>
|
||||||
<Link href={'/' + user.name + '/all'} passHref legacyBehavior>
|
<Link href={'/' + user.name + '/all'} passHref legacyBehavior>
|
||||||
<Nav.Link eventKey>
|
<Nav.Link eventKey='items'>
|
||||||
{numWithUnits(user.nitems, {
|
{numWithUnits(user.nitems, {
|
||||||
abbreviate: false,
|
abbreviate: false,
|
||||||
unitSingular: 'item',
|
unitSingular: 'item',
|
||||||
|
@ -55,6 +59,19 @@ export default function UserHeader ({ user }) {
|
||||||
</Nav.Link>
|
</Nav.Link>
|
||||||
</Link>
|
</Link>
|
||||||
</Nav.Item>
|
</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>
|
</Nav>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { gql } from '@apollo/client'
|
import { gql } from '@apollo/client'
|
||||||
import { COMMENTS, COMMENTS_ITEM_EXT_FIELDS } from './comments'
|
import { COMMENTS, COMMENTS_ITEM_EXT_FIELDS } from './comments'
|
||||||
import { ITEM_FIELDS, ITEM_FULL_FIELDS } from './items'
|
import { ITEM_FIELDS, ITEM_FULL_FIELDS } from './items'
|
||||||
|
import { SUB_FULL_FIELDS } from './subs'
|
||||||
|
|
||||||
export const ME = gql`
|
export const ME = gql`
|
||||||
{
|
{
|
||||||
|
@ -184,6 +185,7 @@ export const USER_FIELDS = gql`
|
||||||
since
|
since
|
||||||
photoId
|
photoId
|
||||||
nitems
|
nitems
|
||||||
|
nterritories
|
||||||
meSubscriptionPosts
|
meSubscriptionPosts
|
||||||
meSubscriptionComments
|
meSubscriptionComments
|
||||||
meMute
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
|
@ -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>
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in New Issue