feat: add rss feeds ui

* list subscribed rss feeds
* add rss feed page
* delete rss feed modal
This commit is contained in:
Hongbo Wu 2023-07-12 15:25:07 +08:00
parent 3444480b95
commit 71fdac626b
5 changed files with 212 additions and 12 deletions

View file

@ -1,6 +1,9 @@
import { gql } from 'graphql-request'
import { gqlFetcher } from '../networkHelpers'
import { Subscription } from '../queries/useGetSubscriptionsQuery'
import {
Subscription,
SubscriptionType,
} from '../queries/useGetSubscriptionsQuery'
type SubscribeResult = {
subscribe: Subscribe
@ -11,16 +14,22 @@ type Subscribe = {
subscriptions: Subscription[]
}
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,7 +38,7 @@ export async function subscribeMutation(
}
`
try {
const data = (await gqlFetcher(mutation)) as SubscribeResult
const data = (await gqlFetcher(mutation, { input })) as SubscribeResult
return data.errorCodes ? undefined : data.subscribe
} catch (error) {
console.log('subscribeMutation error', error)

View file

@ -12,11 +12,12 @@ type Unsubscribe = {
}
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

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,79 @@
import { useRouter } from 'next/router'
import { useCallback, useState } from 'react'
import { Button } from '../../../components/elements/Button'
import { FormInput } from '../../../components/elements/FormElements'
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'
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="Subscribe to new RSS Feed"
path="/settings/rss/new"
/>
<SettingsLayout>
<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>}
<Button style="ctaDarkYellow" css={{}} onClick={subscribe}>
Add
</Button>
</SettingsLayout>
<div data-testid="settings-rss-subscribe-page-tag" />
</>
)
}

View file

@ -0,0 +1,101 @@
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'
}, `}
{`Updated: ${formattedShortDate(subscription.updatedAt)}, `}
</StyledText>
}
/>
)
})
)}
{onDeleteId && (
<ConfirmationModal
message={
'RSS feed will be unsubscribed. This action cannot be undone.'
}
onAccept={async () => {
await onDelete(onDeleteId)
setOnDeleteId('')
}}
onOpenChange={() => setOnDeleteId('')}
/>
)}
</SettingsTable>
)
}