From 3444480b956f08509904c4892fea590a935a9994 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 12 Jul 2023 10:53:32 +0800 Subject: [PATCH 1/5] change the rss-feed cronjob api endpoint --- packages/api/src/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/src/server.ts b/packages/api/src/server.ts index bc87360ae..3a936c60a 100755 --- a/packages/api/src/server.ts +++ b/packages/api/src/server.ts @@ -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()) From 71fdac626bdb62e5c7d90543e2bfd80956d8f805 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 12 Jul 2023 15:25:07 +0800 Subject: [PATCH 2/5] 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('')} + /> + )} + + ) +} From e40afb7706cdc1f06347b01e6d0d41a5128d496c Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 12 Jul 2023 15:42:41 +0800 Subject: [PATCH 3/5] update add feed css --- packages/web/pages/settings/rss/add.tsx | 101 +++++++++++++++------- packages/web/pages/settings/rss/index.tsx | 3 +- 2 files changed, 69 insertions(+), 35 deletions(-) diff --git a/packages/web/pages/settings/rss/add.tsx b/packages/web/pages/settings/rss/add.tsx index 6a6e6f526..cdfac24de 100644 --- a/packages/web/pages/settings/rss/add.tsx +++ b/packages/web/pages/settings/rss/add.tsx @@ -1,7 +1,13 @@ +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' @@ -9,6 +15,13 @@ import { subscribeMutation } from '../../../lib/networking/mutations/subscribeMu 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( @@ -35,43 +48,65 @@ export default function AddRssFeed(): JSX.Element { return ( <> - + - { - e.preventDefault() - setFeedUrl(e.target.value) - }} - disabled={false} - hidden={false} - required={true} + - {errorMessage && {errorMessage}} - + > + +
Add new RSS Feed
+
+ + { + 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 index b33925f73..a08903a6f 100644 --- a/packages/web/pages/settings/rss/index.tsx +++ b/packages/web/pages/settings/rss/index.tsx @@ -75,8 +75,7 @@ export default function Rss(): JSX.Element { subscription.lastFetchedAt ? formattedShortDate(subscription.lastFetchedAt) : 'Never' - }, `} - {`Updated: ${formattedShortDate(subscription.updatedAt)}, `} + }`} } /> From b4347fc9b7a944ca09e0f6cf03920467d9b322a8 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 12 Jul 2023 15:51:16 +0800 Subject: [PATCH 4/5] add back button --- packages/web/pages/settings/rss/add.tsx | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/packages/web/pages/settings/rss/add.tsx b/packages/web/pages/settings/rss/add.tsx index cdfac24de..da7196792 100644 --- a/packages/web/pages/settings/rss/add.tsx +++ b/packages/web/pages/settings/rss/add.tsx @@ -103,9 +103,24 @@ export default function AddRssFeed(): JSX.Element { {errorMessage && ( {errorMessage} )} - + + + +
From 0289efb0b6600fc68dd0cdf90a69016bf46e7e14 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Wed, 12 Jul 2023 16:59:37 +0800 Subject: [PATCH 5/5] fix subscribe and unsubscribe mutation query in web --- packages/web/lib/networking/mutations/subscribeMutation.ts | 4 ++-- .../web/lib/networking/mutations/unsubscribeMutation.ts | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/web/lib/networking/mutations/subscribeMutation.ts b/packages/web/lib/networking/mutations/subscribeMutation.ts index afde99b23..1351592a1 100644 --- a/packages/web/lib/networking/mutations/subscribeMutation.ts +++ b/packages/web/lib/networking/mutations/subscribeMutation.ts @@ -7,11 +7,11 @@ import { type SubscribeResult = { subscribe: Subscribe - errorCodes?: unknown[] } type Subscribe = { subscriptions: Subscription[] + errorCodes?: unknown[] } export type SubscribeMutationInput = { @@ -39,7 +39,7 @@ export async function subscribeMutation( ` try { const data = (await gqlFetcher(mutation, { input })) as SubscribeResult - return data.errorCodes ? undefined : data.subscribe + return data.subscribe.errorCodes ? undefined : data.subscribe } catch (error) { console.log('subscribeMutation error', error) return undefined diff --git a/packages/web/lib/networking/mutations/unsubscribeMutation.ts b/packages/web/lib/networking/mutations/unsubscribeMutation.ts index 1bfcc0e3c..24e2505e1 100644 --- a/packages/web/lib/networking/mutations/unsubscribeMutation.ts +++ b/packages/web/lib/networking/mutations/unsubscribeMutation.ts @@ -4,11 +4,11 @@ import { Subscription } from '../queries/useGetSubscriptionsQuery' type UnsubscribeResult = { unsubscribe: Unsubscribe - errorCodes?: unknown[] } type Unsubscribe = { subscription: Subscription + errorCodes?: unknown[] } export async function unsubscribeMutation( @@ -32,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