Skip to content

Commit dc01ebd

Browse files
brymuthuumn
andauthored
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>
1 parent 874694e commit dc01ebd

File tree

10 files changed

+195
-72
lines changed

10 files changed

+195
-72
lines changed

api/resolvers/sub.js

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ export default {
127127
subs
128128
}
129129
},
130-
userSubs: async (_parent, { name, cursor, when, by, from, to, limit = LIMIT }, { models }) => {
130+
userSubs: async (_parent, { name, cursor, when, by, from, to, limit = LIMIT }, { models, me }) => {
131131
if (!name) {
132132
throw new GqlInputError('must supply user name')
133133
}
@@ -150,26 +150,56 @@ export default {
150150
}
151151

152152
const subs = await models.$queryRawUnsafe(`
153-
SELECT "Sub".*,
154-
"Sub".created_at as "createdAt",
155-
COALESCE(floor(sum(msats_revenue)/1000), 0) as revenue,
156-
COALESCE(floor(sum(msats_stacked)/1000), 0) as stacked,
157-
COALESCE(floor(sum(msats_spent)/1000), 0) as spent,
158-
COALESCE(sum(posts), 0) as nposts,
159-
COALESCE(sum(comments), 0) as ncomments
160-
FROM ${viewGroup(range, 'sub_stats')}
161-
JOIN "Sub" on "Sub".name = u.sub_name
162-
WHERE "Sub"."userId" = $3
163-
AND "Sub".status = 'ACTIVE'
164-
GROUP BY "Sub".name
165-
ORDER BY ${column} DESC NULLS LAST, "Sub".created_at ASC
166-
OFFSET $4
167-
LIMIT $5`, ...range, user.id, decodedCursor.offset, limit)
153+
SELECT "Sub".*,
154+
"Sub".created_at as "createdAt",
155+
COALESCE(floor(sum(msats_revenue)/1000), 0) as revenue,
156+
COALESCE(floor(sum(msats_stacked)/1000), 0) as stacked,
157+
COALESCE(floor(sum(msats_spent)/1000), 0) as spent,
158+
COALESCE(sum(posts), 0) as nposts,
159+
COALESCE(sum(comments), 0) as ncomments,
160+
ss."userId" IS NOT NULL as "meSubscription",
161+
ms."userId" IS NOT NULL as "meMuteSub"
162+
FROM ${viewGroup(range, 'sub_stats')}
163+
JOIN "Sub" on "Sub".name = u.sub_name
164+
LEFT JOIN "SubSubscription" ss ON ss."subName" = "Sub".name AND ss."userId" IS NOT DISTINCT FROM $4
165+
LEFT JOIN "MuteSub" ms ON ms."subName" = "Sub".name AND ms."userId" IS NOT DISTINCT FROM $4
166+
WHERE "Sub"."userId" = $3
167+
AND "Sub".status = 'ACTIVE'
168+
GROUP BY "Sub".name, ss."userId", ms."userId"
169+
ORDER BY ${column} DESC NULLS LAST, "Sub".created_at ASC
170+
OFFSET $5
171+
LIMIT $6
172+
`, ...range, user.id, me?.id, decodedCursor.offset, limit)
168173

169174
return {
170175
cursor: subs.length === limit ? nextCursorEncoded(decodedCursor, limit) : null,
171176
subs
172177
}
178+
},
179+
mySubscribedSubs: async (parent, { cursor }, { models, me }) => {
180+
if (!me) {
181+
throw new GqlAuthenticationError()
182+
}
183+
184+
const decodedCursor = decodeCursor(cursor)
185+
const subs = await models.$queryRaw`
186+
SELECT "Sub".*,
187+
"MuteSub"."userId" IS NOT NULL as "meMuteSub",
188+
TRUE as "meSubscription"
189+
FROM "SubSubscription"
190+
JOIN "Sub" ON "SubSubscription"."subName" = "Sub".name
191+
LEFT JOIN "MuteSub" ON "MuteSub"."subName" = "Sub".name AND "MuteSub"."userId" = ${me.id}
192+
WHERE "SubSubscription"."userId" = ${me.id}
193+
AND "Sub".status <> 'STOPPED'
194+
ORDER BY "Sub".name ASC
195+
OFFSET ${decodedCursor.offset}
196+
LIMIT ${LIMIT}
197+
`
198+
199+
return {
200+
cursor: subs.length === LIMIT ? nextCursorEncoded(decodedCursor, LIMIT) : null,
201+
subs
202+
}
173203
}
174204
},
175205
Mutation: {

api/typeDefs/sub.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export default gql`
77
subs: [Sub!]!
88
topSubs(cursor: String, when: String, from: String, to: String, by: String, limit: Limit): Subs
99
userSubs(name: String!, cursor: String, when: String, from: String, to: String, by: String, limit: Limit): Subs
10+
mySubscribedSubs(cursor: String): Subs
1011
subSuggestions(q: String!, limit: Limit): [Sub!]!
1112
}
1213

components/territory-header.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { createContext, useContext } from 'react'
12
import { Badge, Button, CardFooter, Dropdown } from 'react-bootstrap'
23
import { AccordianCard } from './accordian-item'
34
import TerritoryPaymentDue, { TerritoryBillingLine } from './territory-payment-due'
@@ -13,6 +14,16 @@ import { useToast } from './toast'
1314
import ActionDropdown from './action-dropdown'
1415
import { TerritoryTransferDropdownItem } from './territory-transfer'
1516

17+
const SubscribeTerritoryContext = createContext({ refetchQueries: [] })
18+
19+
export const SubscribeTerritoryContextProvider = ({ children, value }) => (
20+
<SubscribeTerritoryContext.Provider value={value}>
21+
{children}
22+
</SubscribeTerritoryContext.Provider>
23+
)
24+
25+
export const useSubscribeTerritoryContext = () => useContext(SubscribeTerritoryContext)
26+
1627
export function TerritoryDetails ({ sub, children }) {
1728
return (
1829
<AccordianCard
@@ -149,12 +160,15 @@ export default function TerritoryHeader ({ sub }) {
149160

150161
export function MuteSubDropdownItem ({ item, sub }) {
151162
const toaster = useToast()
163+
const { refetchQueries } = useSubscribeTerritoryContext()
152164

153165
const [toggleMuteSub] = useMutation(
154166
gql`
155167
mutation toggleMuteSub($name: String!) {
156168
toggleMuteSub(name: $name)
157169
}`, {
170+
refetchQueries,
171+
awaitRefetchQueries: true,
158172
update (cache, { data: { toggleMuteSub } }) {
159173
cache.modify({
160174
id: `Sub:{"name":"${sub.name}"}`,
@@ -213,11 +227,14 @@ export function PinSubDropdownItem ({ item: { id, position } }) {
213227

214228
export function ToggleSubSubscriptionDropdownItem ({ sub: { name, meSubscription } }) {
215229
const toaster = useToast()
230+
const { refetchQueries } = useSubscribeTerritoryContext()
216231
const [toggleSubSubscription] = useMutation(
217232
gql`
218233
mutation toggleSubSubscription($name: String!) {
219234
toggleSubSubscription(name: $name)
220235
}`, {
236+
refetchQueries,
237+
awaitRefetchQueries: true,
221238
update (cache, { data: { toggleSubSubscription } }) {
222239
cache.modify({
223240
id: `Sub:{"name":"${name}"}`,

components/territory-list.js

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import React, { useEffect, useMemo, useState } from 'react'
55
import { useQuery } from '@apollo/client'
66
import MoreFooter from './more-footer'
77
import { useData } from './use-data'
8+
import { useMe } from './me'
89
import Info from './info'
9-
import { TerritoryInfo } from './territory-header'
10+
import ActionDropdown from './action-dropdown'
11+
import { TerritoryInfo, ToggleSubSubscriptionDropdownItem, MuteSubDropdownItem } from './territory-header'
1012

1113
// all of this nonsense is to show the stat we are sorting by first
1214
const Revenue = ({ sub }) => (sub.optional.revenue !== null && <span>{abbrNum(sub.optional.revenue)} revenue</span>)
@@ -35,16 +37,17 @@ function separate (arr, separator) {
3537
return arr.flatMap((x, i) => i < arr.length - 1 ? [x, separator] : [x])
3638
}
3739

38-
export default function TerritoryList ({ ssrData, query, variables, destructureData, rank }) {
40+
export default function TerritoryList ({ ssrData, query, variables, destructureData, rank, subActionDropdown, statCompsProp = STAT_COMPONENTS }) {
3941
const { data, fetchMore } = useQuery(query, { variables })
4042
const dat = useData(data, ssrData)
41-
const [statComps, setStatComps] = useState(separate(STAT_COMPONENTS, Separator))
43+
const { me } = useMe()
44+
const [statComps, setStatComps] = useState(separate(statCompsProp, Separator))
4245

4346
useEffect(() => {
4447
// shift the stat we are sorting by to the front
45-
const comps = [...STAT_COMPONENTS]
48+
const comps = [...statCompsProp]
4649
setStatComps(separate([...comps.splice(STAT_POS[variables?.by || 0], 1), ...comps], Separator))
47-
}, [variables?.by])
50+
}, [variables?.by], statCompsProp)
4851

4952
const { subs, cursor } = useMemo(() => {
5053
if (!dat) return {}
@@ -77,6 +80,12 @@ export default function TerritoryList ({ ssrData, query, variables, destructureD
7780
{sub.name}
7881
</Link>
7982
<Info className='d-flex'><TerritoryInfo sub={sub} /></Info>
83+
{me && subActionDropdown && (
84+
<ActionDropdown>
85+
<ToggleSubSubscriptionDropdownItem sub={sub} />
86+
<MuteSubDropdownItem sub={sub} />
87+
</ActionDropdown>
88+
)}
8089
</div>
8190
<div className={styles.other}>
8291
{statComps.map((Comp, i) => <Comp key={i} sub={sub} />)}

fragments/users.js

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -216,25 +216,6 @@ export const USER_FIELDS = gql`
216216
...StreakFields
217217
}`
218218

219-
export const MY_SUBSCRIBED_USERS = gql`
220-
${STREAK_FIELDS}
221-
query MySubscribedUsers($cursor: String) {
222-
mySubscribedUsers(cursor: $cursor) {
223-
users {
224-
id
225-
name
226-
photoId
227-
meSubscriptionPosts
228-
meSubscriptionComments
229-
meMute
230-
231-
...StreakFields
232-
}
233-
cursor
234-
}
235-
}
236-
`
237-
238219
export const MY_MUTED_USERS = gql`
239220
${STREAK_FIELDS}
240221
query MyMutedUsers($cursor: String) {
@@ -390,3 +371,33 @@ export const USER_STATS = gql`
390371
}
391372
}
392373
}`
374+
375+
export const MY_SUBSCRIBED_USERS = gql`
376+
${STREAK_FIELDS}
377+
query MySubscribedUsers($cursor: String) {
378+
mySubscribedUsers(cursor: $cursor) {
379+
users {
380+
id
381+
name
382+
photoId
383+
meSubscriptionPosts
384+
meSubscriptionComments
385+
meMute
386+
...StreakFields
387+
}
388+
cursor
389+
}
390+
}
391+
`
392+
393+
export const MY_SUBSCRIBED_SUBS = gql`
394+
${SUB_FULL_FIELDS}
395+
query MySubscribedSubs($cursor: String) {
396+
mySubscribedSubs(cursor: $cursor) {
397+
subs {
398+
...SubFullFields
399+
}
400+
cursor
401+
}
402+
}
403+
`

pages/[name]/territories.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export default function UserTerritories ({ ssrData }) {
2525
query={USER_WITH_SUBS}
2626
variables={variables}
2727
destructureData={data => data.userSubs}
28+
subActionDropdown
2829
rank
2930
/>
3031
</div>

pages/settings/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export function SettingsHeader () {
6868
</Link>
6969
</Nav.Item>
7070
<Nav.Item>
71-
<Link href='/settings/subscriptions' passHref legacyBehavior>
71+
<Link href='/settings/subscriptions/stackers' passHref legacyBehavior>
7272
<Nav.Link eventKey='subscriptions'>subscriptions</Nav.Link>
7373
</Link>
7474
</Nav.Item>

pages/settings/subscriptions/index.js

Lines changed: 0 additions & 31 deletions
This file was deleted.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { useMemo } from 'react'
2+
import { getGetServerSideProps } from '@/api/ssrApollo'
3+
import Layout from '@/components/layout'
4+
import { Select } from '@/components/form'
5+
import UserList from '@/components/user-list'
6+
import { MY_SUBSCRIBED_USERS } from '@/fragments/users'
7+
import { SettingsHeader } from '../index'
8+
import { SubscribeUserContextProvider } from '@/components/subscribeUser'
9+
import { useRouter } from 'next/router'
10+
11+
export const getServerSideProps = getGetServerSideProps({
12+
query: MY_SUBSCRIBED_USERS,
13+
authRequired: true
14+
})
15+
16+
export function SubscriptionLayout ({ subType, children }) {
17+
const router = useRouter()
18+
19+
return (
20+
<Layout>
21+
<div className='pb-3 w-100 mt-2'>
22+
<SettingsHeader />
23+
<Select
24+
name='subscriptionType'
25+
size='sm'
26+
className='w-auto'
27+
noForm
28+
items={['stackers', 'territories']}
29+
value={subType}
30+
onChange={(_, e) => router.push(`/settings/subscriptions/${e.target.value}`)}
31+
/>
32+
{children}
33+
</div>
34+
</Layout>
35+
)
36+
}
37+
38+
export default function MySubscribedUsers ({ ssrData }) {
39+
const subscribeContextValue = useMemo(() => ({ refetchQueries: ['MySubscribedUsers'] }), [])
40+
return (
41+
<SubscriptionLayout subType='stackers'>
42+
<SubscribeUserContextProvider value={subscribeContextValue}>
43+
<UserList
44+
ssrData={ssrData}
45+
query={MY_SUBSCRIBED_USERS}
46+
destructureData={data => data.mySubscribedUsers}
47+
variables={{}}
48+
rank
49+
nymActionDropdown
50+
statCompsProp={[]}
51+
/>
52+
</SubscribeUserContextProvider>
53+
</SubscriptionLayout>
54+
)
55+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { useMemo } from 'react'
2+
import { getGetServerSideProps } from '@/api/ssrApollo'
3+
import { MY_SUBSCRIBED_SUBS } from '@/fragments/users'
4+
import TerritoryList from '@/components/territory-list'
5+
import { SubscribeTerritoryContextProvider } from '@/components/territory-header'
6+
import { SubscriptionLayout } from './stackers'
7+
8+
export const getServerSideProps = getGetServerSideProps({
9+
query: MY_SUBSCRIBED_SUBS,
10+
authRequired: true
11+
})
12+
13+
export default function MySubscribedSubs ({ ssrData }) {
14+
const subscribeContextValue = useMemo(() => ({ refetchQueries: ['MySubscribedSubs'] }), [])
15+
return (
16+
<SubscriptionLayout subType='territories'>
17+
<SubscribeTerritoryContextProvider value={subscribeContextValue}>
18+
<TerritoryList
19+
ssrData={ssrData}
20+
query={MY_SUBSCRIBED_SUBS}
21+
variables={{}}
22+
destructureData={data => data.mySubscribedSubs}
23+
rank
24+
subActionDropdown
25+
statCompsProp={[]}
26+
/>
27+
</SubscribeTerritoryContextProvider>
28+
</SubscriptionLayout>
29+
)
30+
}

0 commit comments

Comments
 (0)