Merge pull request #2481 from omnivore-app/feature/rss-ui

feature/rss ui
This commit is contained in:
Hongbo Wu
2023-07-12 17:02:20 +08:00
committed by GitHub
6 changed files with 268 additions and 17 deletions

View File

@ -158,9 +158,9 @@ export const createApp = (): {
app.use('/svc/pubsub/upload', uploadServiceRouter())
app.use('/svc/pubsub/webhooks', webhooksServiceRouter())
app.use('/svc/pubsub/integrations', integrationsServiceRouter())
app.use('/svc/pubsub/rss-feed', rssFeedRouter())
app.use('/svc/reminders', remindersServiceRouter())
app.use('/svc/email-attachment', emailAttachmentRouter())
app.use('/svc/rss-feed', rssFeedRouter())
if (env.dev.isLocal) {
app.use('/local/debug', localDebugRouter())

View File

@ -1,26 +1,35 @@
import { gql } from 'graphql-request'
import { gqlFetcher } from '../networkHelpers'
import { Subscription } from '../queries/useGetSubscriptionsQuery'
import {
Subscription,
SubscriptionType,
} from '../queries/useGetSubscriptionsQuery'
type SubscribeResult = {
subscribe: Subscribe
errorCodes?: unknown[]
}
type Subscribe = {
subscriptions: Subscription[]
errorCodes?: unknown[]
}
export type SubscribeMutationInput = {
name?: string
url?: string
subscriptionType?: SubscriptionType
}
export async function subscribeMutation(
subscribeName: string
input: SubscribeMutationInput
): Promise<any | undefined> {
const mutation = gql`
mutation {
subscribe(name: "${subscribeName}") {
mutation Subscribe($input: SubscribeInput!) {
subscribe(input: $input) {
... on SubscribeSuccess {
subscriptions {
id
}
id
}
}
... on SubscribeError {
errorCodes
@ -29,8 +38,8 @@ export async function subscribeMutation(
}
`
try {
const data = (await gqlFetcher(mutation)) as SubscribeResult
return data.errorCodes ? undefined : data.subscribe
const data = (await gqlFetcher(mutation, { input })) as SubscribeResult
return data.subscribe.errorCodes ? undefined : data.subscribe
} catch (error) {
console.log('subscribeMutation error', error)
return undefined

View File

@ -4,19 +4,20 @@ import { Subscription } from '../queries/useGetSubscriptionsQuery'
type UnsubscribeResult = {
unsubscribe: Unsubscribe
errorCodes?: unknown[]
}
type Unsubscribe = {
subscription: Subscription
errorCodes?: unknown[]
}
export async function unsubscribeMutation(
subscribeName: string
subscribeName: string,
id = ''
): Promise<any | undefined> {
const mutation = gql`
mutation {
unsubscribe(name: "${subscribeName}") {
unsubscribe(name: "${subscribeName}", subscriptionId: "${id}") {
... on UnsubscribeSuccess {
subscription {
id
@ -31,7 +32,9 @@ export async function unsubscribeMutation(
try {
const data = (await gqlFetcher(mutation)) as UnsubscribeResult
return data.errorCodes ? undefined : data.unsubscribe.subscription.id
return data.unsubscribe.errorCodes
? undefined
: data.unsubscribe.subscription.id
} catch (error) {
console.log('unsubscribeMutation error', error)
return undefined

View File

@ -4,10 +4,15 @@ import { publicGqlFetcher } from '../networkHelpers'
export type SubscriptionStatus = 'ACTIVE' | 'DELETED' | 'UNSUBSCRIBED'
export enum SubscriptionType {
RSS = 'RSS',
NEWSLETTER = 'NEWSLETTER',
}
export type Subscription = {
id: string
name: string
newsletterEmail: string
newsletterEmail?: string
url?: string
description?: string
@ -15,6 +20,7 @@ export type Subscription = {
status: SubscriptionStatus
createdAt: string
updatedAt: string
lastFetchedAt?: string
}
type SubscriptionsQueryResponse = {
@ -31,10 +37,13 @@ type SubscriptionsData = {
subscriptions: unknown
}
export function useGetSubscriptionsQuery(): SubscriptionsQueryResponse {
export function useGetSubscriptionsQuery(
subscriptionType = SubscriptionType.NEWSLETTER,
sortBy = 'UPDATED_TIME'
): SubscriptionsQueryResponse {
const query = gql`
query GetSubscriptions {
subscriptions(sort: { by: UPDATED_TIME }) {
subscriptions(sort: { by: ${sortBy} }, type: ${subscriptionType}) {
... on SubscriptionsSuccess {
subscriptions {
id
@ -47,6 +56,7 @@ export function useGetSubscriptionsQuery(): SubscriptionsQueryResponse {
unsubscribeHttpUrl
createdAt
updatedAt
lastFetchedAt
}
}
... on SubscriptionsError {

View File

@ -0,0 +1,129 @@
import { styled } from '@stitches/react'
import { useRouter } from 'next/router'
import { useCallback, useState } from 'react'
import { Button } from '../../../components/elements/Button'
import { FormInput } from '../../../components/elements/FormElements'
import {
Box,
HStack,
VStack,
} from '../../../components/elements/LayoutPrimitives'
import { StyledText } from '../../../components/elements/StyledText'
import { PageMetaData } from '../../../components/patterns/PageMetaData'
import { SettingsLayout } from '../../../components/templates/SettingsLayout'
import { subscribeMutation } from '../../../lib/networking/mutations/subscribeMutation'
import { SubscriptionType } from '../../../lib/networking/queries/useGetSubscriptionsQuery'
import { showSuccessToast } from '../../../lib/toastHelpers'
// Styles
const Header = styled(Box, {
color: '$utilityTextDefault',
fontSize: 'x-large',
margin: '20px',
})
export default function AddRssFeed(): JSX.Element {
const router = useRouter()
const [errorMessage, setErrorMessage] = useState<string | undefined>(
undefined
)
const [feedUrl, setFeedUrl] = useState<string>('')
const subscribe = useCallback(async () => {
try {
const result = await subscribeMutation({
url: feedUrl,
subscriptionType: SubscriptionType.RSS,
})
if (result) {
router.push(`/settings/rss`)
showSuccessToast('New RSS feed has been added.')
} else {
setErrorMessage('There was an error adding new RSS feed.')
}
} catch (err) {
setErrorMessage('Error: ' + err)
}
}, [feedUrl, router])
return (
<>
<PageMetaData title="Add new RSS Feed" path="/settings/rss/add" />
<SettingsLayout>
<VStack
distribution={'start'}
alignment={'start'}
css={{
margin: '0 auto',
width: '80%',
height: '100%',
}}
>
<HStack
alignment={'start'}
distribution={'start'}
css={{
width: '80%',
pb: '$2',
borderBottom: '1px solid $utilityTextDefault',
pr: '$1',
}}
>
<Header>Add new RSS Feed</Header>
</HStack>
<FormInput
type="url"
key="feedUrl"
value={feedUrl}
placeholder={'Enter the RSS feed URL here'}
onChange={(e) => {
e.preventDefault()
setFeedUrl(e.target.value)
}}
disabled={false}
hidden={false}
required={true}
css={{
border: '1px solid $textNonessential',
borderRadius: '8px',
width: '80%',
bg: 'transparent',
fontSize: '16px',
textIndent: '8px',
my: '20px',
height: '38px',
color: '$grayTextContrast',
'&:focus': {
outline: 'none',
boxShadow: '0px 0px 2px 2px rgba(255, 234, 159, 0.56)',
},
}}
/>
{errorMessage && (
<StyledText style="error">{errorMessage}</StyledText>
)}
<HStack>
<Button
style="ctaDarkYellow"
css={{ marginRight: '10px' }}
onClick={subscribe}
>
Add
</Button>
<Button
style="ctaGray"
css={{}}
onClick={async () => {
router.push('/settings/rss')
}}
>
Back
</Button>
</HStack>
</VStack>
</SettingsLayout>
<div data-testid="settings-rss-subscribe-page-tag" />
</>
)
}

View File

@ -0,0 +1,100 @@
import { useRouter } from 'next/router'
import { useState } from 'react'
import { StyledText } from '../../../components/elements/StyledText'
import { ConfirmationModal } from '../../../components/patterns/ConfirmationModal'
import {
EmptySettingsRow,
SettingsTable,
SettingsTableRow,
} from '../../../components/templates/settings/SettingsTable'
import { formattedShortDate } from '../../../lib/dateFormatting'
import { unsubscribeMutation } from '../../../lib/networking/mutations/unsubscribeMutation'
import {
SubscriptionType,
useGetSubscriptionsQuery,
} from '../../../lib/networking/queries/useGetSubscriptionsQuery'
import { applyStoredTheme } from '../../../lib/themeUpdater'
import { showErrorToast, showSuccessToast } from '../../../lib/toastHelpers'
export default function Rss(): JSX.Element {
const router = useRouter()
const { subscriptions, revalidate, isValidating } = useGetSubscriptionsQuery(
SubscriptionType.RSS
)
const [onDeleteId, setOnDeleteId] = useState<string>('')
async function onDelete(id: string): Promise<void> {
const result = await unsubscribeMutation('', id)
if (result) {
showSuccessToast('RSS feed unsubscribed', { position: 'bottom-right' })
} else {
showErrorToast('Failed to unsubscribe', { position: 'bottom-right' })
}
revalidate()
}
applyStoredTheme(false)
return (
<SettingsTable
pageId={'rss'}
pageInfoLink={''} // TODO: https://docs.omnivore.app/integrations/rss.html
headerTitle={'Subscribed RSS feeds'}
createTitle={'Add RSS feed'}
createAction={() => {
router.push('/settings/rss/add')
}}
>
{subscriptions.length === 0 ? (
<EmptySettingsRow
text={isValidating ? '-' : 'No RSS feeds subscribed'}
/>
) : (
subscriptions.map((subscription, i) => {
return (
<SettingsTableRow
key={subscription.id}
title={subscription.name}
isLast={i === subscriptions.length - 1}
onDelete={() => {
console.log('onDelete triggered: ', subscription.id)
setOnDeleteId(subscription.id)
}}
deleteTitle="Delete"
sublineElement={
<StyledText
css={{
my: '5px',
fontSize: '11px',
a: {
color: '$omnivoreCtaYellow',
},
}}
>
{`Last fetched: ${
subscription.lastFetchedAt
? formattedShortDate(subscription.lastFetchedAt)
: 'Never'
}`}
</StyledText>
}
/>
)
})
)}
{onDeleteId && (
<ConfirmationModal
message={
'RSS feed will be unsubscribed. This action cannot be undone.'
}
onAccept={async () => {
await onDelete(onDeleteId)
setOnDeleteId('')
}}
onOpenChange={() => setOnDeleteId('')}
/>
)}
</SettingsTable>
)
}