From 71fdac626bdb62e5c7d90543e2bfd80956d8f805 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 12 Jul 2023 15:25:07 +0800 Subject: [PATCH] feat: add rss feeds ui * list subscribed rss feeds * add rss feed page * delete rss feed modal --- .../networking/mutations/subscribeMutation.ts | 23 ++-- .../mutations/unsubscribeMutation.ts | 5 +- .../queries/useGetSubscriptionsQuery.tsx | 16 ++- packages/web/pages/settings/rss/add.tsx | 79 ++++++++++++++ packages/web/pages/settings/rss/index.tsx | 101 ++++++++++++++++++ 5 files changed, 212 insertions(+), 12 deletions(-) create mode 100644 packages/web/pages/settings/rss/add.tsx create mode 100644 packages/web/pages/settings/rss/index.tsx diff --git a/packages/web/lib/networking/mutations/subscribeMutation.ts b/packages/web/lib/networking/mutations/subscribeMutation.ts index 5dd599f82..afde99b23 100644 --- a/packages/web/lib/networking/mutations/subscribeMutation.ts +++ b/packages/web/lib/networking/mutations/subscribeMutation.ts @@ -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 { 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) diff --git a/packages/web/lib/networking/mutations/unsubscribeMutation.ts b/packages/web/lib/networking/mutations/unsubscribeMutation.ts index 3eba481ab..1bfcc0e3c 100644 --- a/packages/web/lib/networking/mutations/unsubscribeMutation.ts +++ b/packages/web/lib/networking/mutations/unsubscribeMutation.ts @@ -12,11 +12,12 @@ type Unsubscribe = { } export async function unsubscribeMutation( - subscribeName: string + subscribeName: string, + id = '' ): Promise { const mutation = gql` mutation { - unsubscribe(name: "${subscribeName}") { + unsubscribe(name: "${subscribeName}", subscriptionId: "${id}") { ... on UnsubscribeSuccess { subscription { id diff --git a/packages/web/lib/networking/queries/useGetSubscriptionsQuery.tsx b/packages/web/lib/networking/queries/useGetSubscriptionsQuery.tsx index 051b20d70..48fcef432 100644 --- a/packages/web/lib/networking/queries/useGetSubscriptionsQuery.tsx +++ b/packages/web/lib/networking/queries/useGetSubscriptionsQuery.tsx @@ -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 { diff --git a/packages/web/pages/settings/rss/add.tsx b/packages/web/pages/settings/rss/add.tsx new file mode 100644 index 000000000..6a6e6f526 --- /dev/null +++ b/packages/web/pages/settings/rss/add.tsx @@ -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( + undefined + ) + const [feedUrl, setFeedUrl] = useState('') + + 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 ( + <> + + + { + 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 && {errorMessage}} + + +
+ + ) +} diff --git a/packages/web/pages/settings/rss/index.tsx b/packages/web/pages/settings/rss/index.tsx new file mode 100644 index 000000000..b33925f73 --- /dev/null +++ b/packages/web/pages/settings/rss/index.tsx @@ -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('') + + async function onDelete(id: string): Promise { + 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 ( + { + router.push('/settings/rss/add') + }} + > + {subscriptions.length === 0 ? ( + + ) : ( + subscriptions.map((subscription, i) => { + return ( + { + console.log('onDelete triggered: ', subscription.id) + setOnDeleteId(subscription.id) + }} + deleteTitle="Delete" + sublineElement={ + + {`Last fetched: ${ + subscription.lastFetchedAt + ? formattedShortDate(subscription.lastFetchedAt) + : 'Never' + }, `} + {`Updated: ${formattedShortDate(subscription.updatedAt)}, `} + + } + /> + ) + }) + )} + + {onDeleteId && ( + { + await onDelete(onDeleteId) + setOnDeleteId('') + }} + onOpenChange={() => setOnDeleteId('')} + /> + )} + + ) +}