mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
feat: add rss feeds ui
* list subscribed rss feeds * add rss feed page * delete rss feed modal
This commit is contained in:
parent
3444480b95
commit
71fdac626b
5 changed files with 212 additions and 12 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
79
packages/web/pages/settings/rss/add.tsx
Normal file
79
packages/web/pages/settings/rss/add.tsx
Normal 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" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
101
packages/web/pages/settings/rss/index.tsx
Normal file
101
packages/web/pages/settings/rss/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
Loading…
Reference in a new issue