Add Territory Sub management tab in Subscriptions (#2191)
* Add Territory Sub management tab in Subscriptions * don't use queryRawUnsafe * auto width on select * separate into pages for browser nav * fix multiple separators * simplify queries --------- Co-authored-by: k00b <k00b@stacker.news>
This commit is contained in:
parent
874694eb10
commit
dc01ebdb26
@ -127,7 +127,7 @@ export default {
|
||||
subs
|
||||
}
|
||||
},
|
||||
userSubs: async (_parent, { name, cursor, when, by, from, to, limit = LIMIT }, { models }) => {
|
||||
userSubs: async (_parent, { name, cursor, when, by, from, to, limit = LIMIT }, { models, me }) => {
|
||||
if (!name) {
|
||||
throw new GqlInputError('must supply user name')
|
||||
}
|
||||
@ -150,26 +150,56 @@ export default {
|
||||
}
|
||||
|
||||
const subs = await models.$queryRawUnsafe(`
|
||||
SELECT "Sub".*,
|
||||
"Sub".created_at as "createdAt",
|
||||
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 ${viewGroup(range, 'sub_stats')}
|
||||
JOIN "Sub" on "Sub".name = u.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)
|
||||
SELECT "Sub".*,
|
||||
"Sub".created_at as "createdAt",
|
||||
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,
|
||||
ss."userId" IS NOT NULL as "meSubscription",
|
||||
ms."userId" IS NOT NULL as "meMuteSub"
|
||||
FROM ${viewGroup(range, 'sub_stats')}
|
||||
JOIN "Sub" on "Sub".name = u.sub_name
|
||||
LEFT JOIN "SubSubscription" ss ON ss."subName" = "Sub".name AND ss."userId" IS NOT DISTINCT FROM $4
|
||||
LEFT JOIN "MuteSub" ms ON ms."subName" = "Sub".name AND ms."userId" IS NOT DISTINCT FROM $4
|
||||
WHERE "Sub"."userId" = $3
|
||||
AND "Sub".status = 'ACTIVE'
|
||||
GROUP BY "Sub".name, ss."userId", ms."userId"
|
||||
ORDER BY ${column} DESC NULLS LAST, "Sub".created_at ASC
|
||||
OFFSET $5
|
||||
LIMIT $6
|
||||
`, ...range, user.id, me?.id, decodedCursor.offset, limit)
|
||||
|
||||
return {
|
||||
cursor: subs.length === limit ? nextCursorEncoded(decodedCursor, limit) : null,
|
||||
subs
|
||||
}
|
||||
},
|
||||
mySubscribedSubs: async (parent, { cursor }, { models, me }) => {
|
||||
if (!me) {
|
||||
throw new GqlAuthenticationError()
|
||||
}
|
||||
|
||||
const decodedCursor = decodeCursor(cursor)
|
||||
const subs = await models.$queryRaw`
|
||||
SELECT "Sub".*,
|
||||
"MuteSub"."userId" IS NOT NULL as "meMuteSub",
|
||||
TRUE as "meSubscription"
|
||||
FROM "SubSubscription"
|
||||
JOIN "Sub" ON "SubSubscription"."subName" = "Sub".name
|
||||
LEFT JOIN "MuteSub" ON "MuteSub"."subName" = "Sub".name AND "MuteSub"."userId" = ${me.id}
|
||||
WHERE "SubSubscription"."userId" = ${me.id}
|
||||
AND "Sub".status <> 'STOPPED'
|
||||
ORDER BY "Sub".name ASC
|
||||
OFFSET ${decodedCursor.offset}
|
||||
LIMIT ${LIMIT}
|
||||
`
|
||||
|
||||
return {
|
||||
cursor: subs.length === LIMIT ? nextCursorEncoded(decodedCursor, LIMIT) : null,
|
||||
subs
|
||||
}
|
||||
}
|
||||
},
|
||||
Mutation: {
|
||||
|
@ -7,6 +7,7 @@ export default gql`
|
||||
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
|
||||
mySubscribedSubs(cursor: String): Subs
|
||||
subSuggestions(q: String!, limit: Limit): [Sub!]!
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { createContext, useContext } from 'react'
|
||||
import { Badge, Button, CardFooter, Dropdown } from 'react-bootstrap'
|
||||
import { AccordianCard } from './accordian-item'
|
||||
import TerritoryPaymentDue, { TerritoryBillingLine } from './territory-payment-due'
|
||||
@ -13,6 +14,16 @@ import { useToast } from './toast'
|
||||
import ActionDropdown from './action-dropdown'
|
||||
import { TerritoryTransferDropdownItem } from './territory-transfer'
|
||||
|
||||
const SubscribeTerritoryContext = createContext({ refetchQueries: [] })
|
||||
|
||||
export const SubscribeTerritoryContextProvider = ({ children, value }) => (
|
||||
<SubscribeTerritoryContext.Provider value={value}>
|
||||
{children}
|
||||
</SubscribeTerritoryContext.Provider>
|
||||
)
|
||||
|
||||
export const useSubscribeTerritoryContext = () => useContext(SubscribeTerritoryContext)
|
||||
|
||||
export function TerritoryDetails ({ sub, children }) {
|
||||
return (
|
||||
<AccordianCard
|
||||
@ -149,12 +160,15 @@ export default function TerritoryHeader ({ sub }) {
|
||||
|
||||
export function MuteSubDropdownItem ({ item, sub }) {
|
||||
const toaster = useToast()
|
||||
const { refetchQueries } = useSubscribeTerritoryContext()
|
||||
|
||||
const [toggleMuteSub] = useMutation(
|
||||
gql`
|
||||
mutation toggleMuteSub($name: String!) {
|
||||
toggleMuteSub(name: $name)
|
||||
}`, {
|
||||
refetchQueries,
|
||||
awaitRefetchQueries: true,
|
||||
update (cache, { data: { toggleMuteSub } }) {
|
||||
cache.modify({
|
||||
id: `Sub:{"name":"${sub.name}"}`,
|
||||
@ -213,11 +227,14 @@ export function PinSubDropdownItem ({ item: { id, position } }) {
|
||||
|
||||
export function ToggleSubSubscriptionDropdownItem ({ sub: { name, meSubscription } }) {
|
||||
const toaster = useToast()
|
||||
const { refetchQueries } = useSubscribeTerritoryContext()
|
||||
const [toggleSubSubscription] = useMutation(
|
||||
gql`
|
||||
mutation toggleSubSubscription($name: String!) {
|
||||
toggleSubSubscription(name: $name)
|
||||
}`, {
|
||||
refetchQueries,
|
||||
awaitRefetchQueries: true,
|
||||
update (cache, { data: { toggleSubSubscription } }) {
|
||||
cache.modify({
|
||||
id: `Sub:{"name":"${name}"}`,
|
||||
|
@ -5,8 +5,10 @@ import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { useQuery } from '@apollo/client'
|
||||
import MoreFooter from './more-footer'
|
||||
import { useData } from './use-data'
|
||||
import { useMe } from './me'
|
||||
import Info from './info'
|
||||
import { TerritoryInfo } from './territory-header'
|
||||
import ActionDropdown from './action-dropdown'
|
||||
import { TerritoryInfo, ToggleSubSubscriptionDropdownItem, MuteSubDropdownItem } from './territory-header'
|
||||
|
||||
// all of this nonsense is to show the stat we are sorting by first
|
||||
const Revenue = ({ sub }) => (sub.optional.revenue !== null && <span>{abbrNum(sub.optional.revenue)} revenue</span>)
|
||||
@ -35,16 +37,17 @@ function separate (arr, separator) {
|
||||
return arr.flatMap((x, i) => i < arr.length - 1 ? [x, separator] : [x])
|
||||
}
|
||||
|
||||
export default function TerritoryList ({ ssrData, query, variables, destructureData, rank }) {
|
||||
export default function TerritoryList ({ ssrData, query, variables, destructureData, rank, subActionDropdown, statCompsProp = STAT_COMPONENTS }) {
|
||||
const { data, fetchMore } = useQuery(query, { variables })
|
||||
const dat = useData(data, ssrData)
|
||||
const [statComps, setStatComps] = useState(separate(STAT_COMPONENTS, Separator))
|
||||
const { me } = useMe()
|
||||
const [statComps, setStatComps] = useState(separate(statCompsProp, Separator))
|
||||
|
||||
useEffect(() => {
|
||||
// shift the stat we are sorting by to the front
|
||||
const comps = [...STAT_COMPONENTS]
|
||||
const comps = [...statCompsProp]
|
||||
setStatComps(separate([...comps.splice(STAT_POS[variables?.by || 0], 1), ...comps], Separator))
|
||||
}, [variables?.by])
|
||||
}, [variables?.by], statCompsProp)
|
||||
|
||||
const { subs, cursor } = useMemo(() => {
|
||||
if (!dat) return {}
|
||||
@ -77,6 +80,12 @@ export default function TerritoryList ({ ssrData, query, variables, destructureD
|
||||
{sub.name}
|
||||
</Link>
|
||||
<Info className='d-flex'><TerritoryInfo sub={sub} /></Info>
|
||||
{me && subActionDropdown && (
|
||||
<ActionDropdown>
|
||||
<ToggleSubSubscriptionDropdownItem sub={sub} />
|
||||
<MuteSubDropdownItem sub={sub} />
|
||||
</ActionDropdown>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.other}>
|
||||
{statComps.map((Comp, i) => <Comp key={i} sub={sub} />)}
|
||||
|
@ -216,25 +216,6 @@ export const USER_FIELDS = gql`
|
||||
...StreakFields
|
||||
}`
|
||||
|
||||
export const MY_SUBSCRIBED_USERS = gql`
|
||||
${STREAK_FIELDS}
|
||||
query MySubscribedUsers($cursor: String) {
|
||||
mySubscribedUsers(cursor: $cursor) {
|
||||
users {
|
||||
id
|
||||
name
|
||||
photoId
|
||||
meSubscriptionPosts
|
||||
meSubscriptionComments
|
||||
meMute
|
||||
|
||||
...StreakFields
|
||||
}
|
||||
cursor
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const MY_MUTED_USERS = gql`
|
||||
${STREAK_FIELDS}
|
||||
query MyMutedUsers($cursor: String) {
|
||||
@ -390,3 +371,33 @@ export const USER_STATS = gql`
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
export const MY_SUBSCRIBED_USERS = gql`
|
||||
${STREAK_FIELDS}
|
||||
query MySubscribedUsers($cursor: String) {
|
||||
mySubscribedUsers(cursor: $cursor) {
|
||||
users {
|
||||
id
|
||||
name
|
||||
photoId
|
||||
meSubscriptionPosts
|
||||
meSubscriptionComments
|
||||
meMute
|
||||
...StreakFields
|
||||
}
|
||||
cursor
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const MY_SUBSCRIBED_SUBS = gql`
|
||||
${SUB_FULL_FIELDS}
|
||||
query MySubscribedSubs($cursor: String) {
|
||||
mySubscribedSubs(cursor: $cursor) {
|
||||
subs {
|
||||
...SubFullFields
|
||||
}
|
||||
cursor
|
||||
}
|
||||
}
|
||||
`
|
||||
|
@ -25,6 +25,7 @@ export default function UserTerritories ({ ssrData }) {
|
||||
query={USER_WITH_SUBS}
|
||||
variables={variables}
|
||||
destructureData={data => data.userSubs}
|
||||
subActionDropdown
|
||||
rank
|
||||
/>
|
||||
</div>
|
||||
|
@ -68,7 +68,7 @@ export function SettingsHeader () {
|
||||
</Link>
|
||||
</Nav.Item>
|
||||
<Nav.Item>
|
||||
<Link href='/settings/subscriptions' passHref legacyBehavior>
|
||||
<Link href='/settings/subscriptions/stackers' passHref legacyBehavior>
|
||||
<Nav.Link eventKey='subscriptions'>subscriptions</Nav.Link>
|
||||
</Link>
|
||||
</Nav.Item>
|
||||
|
@ -1,31 +0,0 @@
|
||||
import { useMemo } from 'react'
|
||||
import { getGetServerSideProps } from '@/api/ssrApollo'
|
||||
import Layout from '@/components/layout'
|
||||
import UserList from '@/components/user-list'
|
||||
import { MY_SUBSCRIBED_USERS } from '@/fragments/users'
|
||||
import { SettingsHeader } from '../index'
|
||||
import { SubscribeUserContextProvider } from '@/components/subscribeUser'
|
||||
|
||||
export const getServerSideProps = getGetServerSideProps({ query: MY_SUBSCRIBED_USERS, authRequired: true })
|
||||
|
||||
export default function MySubscribedUsers ({ ssrData }) {
|
||||
const subscribeUserContextValue = useMemo(() => ({ refetchQueries: ['MySubscribedUsers'] }), [])
|
||||
return (
|
||||
<Layout>
|
||||
<div className='pb-3 w-100 mt-2'>
|
||||
<SettingsHeader />
|
||||
<div className='mb-4 text-muted'>These here are stackers you've hitched your wagon to, pardner.</div>
|
||||
<SubscribeUserContextProvider value={subscribeUserContextValue}>
|
||||
<UserList
|
||||
ssrData={ssrData} query={MY_SUBSCRIBED_USERS}
|
||||
destructureData={data => data.mySubscribedUsers}
|
||||
variables={{}}
|
||||
rank
|
||||
nymActionDropdown
|
||||
statCompsProp={[]}
|
||||
/>
|
||||
</SubscribeUserContextProvider>
|
||||
</div>
|
||||
</Layout>
|
||||
)
|
||||
}
|
55
pages/settings/subscriptions/stackers.js
Normal file
55
pages/settings/subscriptions/stackers.js
Normal file
@ -0,0 +1,55 @@
|
||||
import { useMemo } from 'react'
|
||||
import { getGetServerSideProps } from '@/api/ssrApollo'
|
||||
import Layout from '@/components/layout'
|
||||
import { Select } from '@/components/form'
|
||||
import UserList from '@/components/user-list'
|
||||
import { MY_SUBSCRIBED_USERS } from '@/fragments/users'
|
||||
import { SettingsHeader } from '../index'
|
||||
import { SubscribeUserContextProvider } from '@/components/subscribeUser'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
export const getServerSideProps = getGetServerSideProps({
|
||||
query: MY_SUBSCRIBED_USERS,
|
||||
authRequired: true
|
||||
})
|
||||
|
||||
export function SubscriptionLayout ({ subType, children }) {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className='pb-3 w-100 mt-2'>
|
||||
<SettingsHeader />
|
||||
<Select
|
||||
name='subscriptionType'
|
||||
size='sm'
|
||||
className='w-auto'
|
||||
noForm
|
||||
items={['stackers', 'territories']}
|
||||
value={subType}
|
||||
onChange={(_, e) => router.push(`/settings/subscriptions/${e.target.value}`)}
|
||||
/>
|
||||
{children}
|
||||
</div>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
export default function MySubscribedUsers ({ ssrData }) {
|
||||
const subscribeContextValue = useMemo(() => ({ refetchQueries: ['MySubscribedUsers'] }), [])
|
||||
return (
|
||||
<SubscriptionLayout subType='stackers'>
|
||||
<SubscribeUserContextProvider value={subscribeContextValue}>
|
||||
<UserList
|
||||
ssrData={ssrData}
|
||||
query={MY_SUBSCRIBED_USERS}
|
||||
destructureData={data => data.mySubscribedUsers}
|
||||
variables={{}}
|
||||
rank
|
||||
nymActionDropdown
|
||||
statCompsProp={[]}
|
||||
/>
|
||||
</SubscribeUserContextProvider>
|
||||
</SubscriptionLayout>
|
||||
)
|
||||
}
|
30
pages/settings/subscriptions/territories.js
Normal file
30
pages/settings/subscriptions/territories.js
Normal file
@ -0,0 +1,30 @@
|
||||
import { useMemo } from 'react'
|
||||
import { getGetServerSideProps } from '@/api/ssrApollo'
|
||||
import { MY_SUBSCRIBED_SUBS } from '@/fragments/users'
|
||||
import TerritoryList from '@/components/territory-list'
|
||||
import { SubscribeTerritoryContextProvider } from '@/components/territory-header'
|
||||
import { SubscriptionLayout } from './stackers'
|
||||
|
||||
export const getServerSideProps = getGetServerSideProps({
|
||||
query: MY_SUBSCRIBED_SUBS,
|
||||
authRequired: true
|
||||
})
|
||||
|
||||
export default function MySubscribedSubs ({ ssrData }) {
|
||||
const subscribeContextValue = useMemo(() => ({ refetchQueries: ['MySubscribedSubs'] }), [])
|
||||
return (
|
||||
<SubscriptionLayout subType='territories'>
|
||||
<SubscribeTerritoryContextProvider value={subscribeContextValue}>
|
||||
<TerritoryList
|
||||
ssrData={ssrData}
|
||||
query={MY_SUBSCRIBED_SUBS}
|
||||
variables={{}}
|
||||
destructureData={data => data.mySubscribedSubs}
|
||||
rank
|
||||
subActionDropdown
|
||||
statCompsProp={[]}
|
||||
/>
|
||||
</SubscribeTerritoryContextProvider>
|
||||
</SubscriptionLayout>
|
||||
)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user