From 2070fa79fa8425a283b0abc0e60d8a34e5ad46f8 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 21 Apr 2022 10:05:28 +0800 Subject: [PATCH 1/9] add subscription to articles api response --- yarn.lock | 54 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3f11d970d..f31029599 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7967,6 +7967,13 @@ dependencies: "@types/react" "*" +"@types/react-syntax-highlighter@11.0.5": + version "11.0.5" + resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.5.tgz#0d546261b4021e1f9d85b50401c0a42acb106087" + integrity sha512-VIOi9i2Oj5XsmWWoB72p3KlZoEbdRAcechJa8Ztebw7bDl2YmR+odxIqhtJGp1q2EozHs02US+gzxJ9nuf56qg== + dependencies: + "@types/react" "*" + "@types/react@*": version "17.0.19" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.19.tgz#8f2a85e8180a43b57966b237d26a29481dacc991" @@ -8469,6 +8476,13 @@ dependencies: "@xtuc/long" "4.2.2" +"@webassemblyjs/leb128@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95" + integrity sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw== + dependencies: + "@xtuc/long" "4.2.2" + "@webassemblyjs/utf8@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff" @@ -8602,6 +8616,15 @@ "@webassemblyjs/wast-parser" "1.9.0" "@xtuc/long" "4.2.2" +"@webassemblyjs/wast-printer@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899" + integrity sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/wast-parser" "1.9.0" + "@xtuc/long" "4.2.2" + "@webpack-cli/configtest@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.1.1.tgz#9f53b1b7946a6efc2a749095a4f450e2932e8356" @@ -10210,27 +10233,6 @@ c8@^7.6.0: yargs "^16.2.0" yargs-parser "^20.2.9" -cacache@^12.0.2: - version "12.0.4" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c" - integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ== - dependencies: - bluebird "^3.5.5" - chownr "^1.1.1" - figgy-pudding "^3.5.1" - glob "^7.1.4" - graceful-fs "^4.1.15" - infer-owner "^1.0.3" - lru-cache "^5.1.1" - mississippi "^3.0.0" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - promise-inflight "^1.0.1" - rimraf "^2.6.3" - ssri "^6.0.1" - unique-filename "^1.1.1" - y18n "^4.0.0" - cacache@^15.0.5, cacache@^15.2.0: version "15.2.0" resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.2.0.tgz#73af75f77c58e72d8c630a7a2858cb18ef523389" @@ -20819,6 +20821,14 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" +randomfill@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" + integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== + dependencies: + randombytes "^2.0.5" + safe-buffer "^5.1.0" + range-parser@^1.2.1, range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" @@ -25175,7 +25185,7 @@ yargs-parser@^18.1.2, yargs-parser@^18.1.3: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^20.2.2, yargs-parser@^20.2.3, yargs-parser@^20.2.9: +yargs-parser@^20.2.2, yargs-parser@^20.2.9: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== From 6842c4c1ad576fd50ef6406cea06808739483e9a Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 21 Apr 2022 11:14:07 +0800 Subject: [PATCH 2/9] revert yarn.lock changes --- yarn.lock | 52 ++-------------------------------------------------- 1 file changed, 2 insertions(+), 50 deletions(-) diff --git a/yarn.lock b/yarn.lock index f31029599..5462e7404 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3053,7 +3053,7 @@ dependencies: "@istanbuljs/schema" "^0.1.2" -"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": +"@istanbuljs/schema@^0.1.2": version "0.1.3" resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== @@ -3293,14 +3293,6 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@jridgewell/trace-mapping@^0.3.7": - version "0.3.9" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" - integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@lerna/add@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/add/-/add-4.0.0.tgz#c36f57d132502a57b9e7058d1548b7a565ef183f" @@ -7967,13 +7959,6 @@ dependencies: "@types/react" "*" -"@types/react-syntax-highlighter@11.0.5": - version "11.0.5" - resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.5.tgz#0d546261b4021e1f9d85b50401c0a42acb106087" - integrity sha512-VIOi9i2Oj5XsmWWoB72p3KlZoEbdRAcechJa8Ztebw7bDl2YmR+odxIqhtJGp1q2EozHs02US+gzxJ9nuf56qg== - dependencies: - "@types/react" "*" - "@types/react@*": version "17.0.19" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.19.tgz#8f2a85e8180a43b57966b237d26a29481dacc991" @@ -8476,13 +8461,6 @@ dependencies: "@xtuc/long" "4.2.2" -"@webassemblyjs/leb128@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95" - integrity sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw== - dependencies: - "@xtuc/long" "4.2.2" - "@webassemblyjs/utf8@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff" @@ -8616,15 +8594,6 @@ "@webassemblyjs/wast-parser" "1.9.0" "@xtuc/long" "4.2.2" -"@webassemblyjs/wast-printer@1.9.0": - version "1.9.0" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899" - integrity sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/wast-parser" "1.9.0" - "@xtuc/long" "4.2.2" - "@webpack-cli/configtest@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.1.1.tgz#9f53b1b7946a6efc2a749095a4f450e2932e8356" @@ -16189,7 +16158,7 @@ istanbul-lib-source-maps@^4.0.0: istanbul-lib-coverage "^3.0.0" source-map "^0.6.1" -istanbul-reports@^3.0.2, istanbul-reports@^3.1.3, istanbul-reports@^3.1.4: +istanbul-reports@^3.0.2, istanbul-reports@^3.1.3: version "3.1.4" resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.4.tgz#1b6f068ecbc6c331040aab5741991273e609e40c" integrity sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw== @@ -20821,14 +20790,6 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" -randomfill@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" - integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== - dependencies: - randombytes "^2.0.5" - safe-buffer "^5.1.0" - range-parser@^1.2.1, range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" @@ -24318,15 +24279,6 @@ v8-to-istanbul@^8.1.0: convert-source-map "^1.6.0" source-map "^0.7.3" -v8-to-istanbul@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.0.0.tgz#be0dae58719fc53cb97e5c7ac1d7e6d4f5b19511" - integrity sha512-HcvgY/xaRm7isYmyx+lFKA4uQmfUbN0J4M0nNItvzTvH/iQ9kW5j/t4YSR+Ge323/lrgDAWJoF46tzGQHwBHFw== - dependencies: - "@jridgewell/trace-mapping" "^0.3.7" - "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^1.6.0" - v8flags@^2.0.10: version "2.1.1" resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4" From f8bacf77dfb2542b97246113fedaa6c5757f564a Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 21 Apr 2022 11:10:18 +0800 Subject: [PATCH 3/9] add list subscriptions --- .../queries/useGetSubscriptionsQuery.tsx | 67 +++++++++++++++++ packages/web/pages/settings/subscriptions.tsx | 74 +++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 packages/web/lib/networking/queries/useGetSubscriptionsQuery.tsx create mode 100644 packages/web/pages/settings/subscriptions.tsx diff --git a/packages/web/lib/networking/queries/useGetSubscriptionsQuery.tsx b/packages/web/lib/networking/queries/useGetSubscriptionsQuery.tsx new file mode 100644 index 000000000..89d0d5528 --- /dev/null +++ b/packages/web/lib/networking/queries/useGetSubscriptionsQuery.tsx @@ -0,0 +1,67 @@ +import { gql } from 'graphql-request' +import useSWR from 'swr' +import { publicGqlFetcher } from '../networkHelpers' +import { Subscription } from '@omnivore/api/src/generated/graphql' + +type SubscriptionsQueryResponse = { + isValidating: boolean + subscriptions: Subscription[] + revalidate: () => void +} + +type SubscriptionsResponseData = { + subscriptions: SubscriptionsData +} + +type SubscriptionsData = { + subscriptions: unknown +} + +export function useGetSubscriptionsQuery(): SubscriptionsQueryResponse { + const query = gql` + query GetSubscriptions { + subscriptions { + ... on SubscriptionsSuccess { + subscriptions { + id + name + url + description + status + unsubscribeMailTo + unsubscribeHttpUrl + createdAt + updatedAt + } + } + ... on SubscriptionsError { + errorCodes + } + } + } + ` + + const { data, mutate, error, isValidating } = useSWR(query, publicGqlFetcher) + + try { + if (data) { + const result = data as SubscriptionsResponseData + const subscriptions = result.subscriptions.subscriptions as Subscription[] + return { + isValidating, + subscriptions, + revalidate: () => { + mutate() + }, + } + } + } catch (error) { + console.log('error', error) + } + return { + isValidating: false, + subscriptions: [], + // eslint-disable-next-line @typescript-eslint/no-empty-function + revalidate: () => {}, + } +} diff --git a/packages/web/pages/settings/subscriptions.tsx b/packages/web/pages/settings/subscriptions.tsx new file mode 100644 index 000000000..6c6db5e07 --- /dev/null +++ b/packages/web/pages/settings/subscriptions.tsx @@ -0,0 +1,74 @@ +import { useState } from 'react' +import { PrimaryLayout } from '../../components/templates/PrimaryLayout' +import { styled } from '../../components/tokens/stitches.config' +import { Box, VStack } from '../../components/elements/LayoutPrimitives' +import { Toaster } from 'react-hot-toast' +import { applyStoredTheme } from '../../lib/themeUpdater' +import { StyledText } from '../../components/elements/StyledText' +import { ConfirmationModal } from '../../components/patterns/ConfirmationModal' +import { useGetSubscriptionsQuery } from '../../lib/networking/queries/useGetSubscriptionsQuery' + +const HeaderWrapper = styled(Box, { + width: '100%', +}) + +export default function SubscriptionsPage(): JSX.Element { + const { subscriptions, revalidate } = useGetSubscriptionsQuery() + const [confirmUnsubscribeName, setConfirmUnsubscribeName] = + useState(null) + + applyStoredTheme(false) + + async function onUnsubscribe(name: string): Promise { + // TODO: unsubscribe from the server + // const result = await deleteLabelMutation(name) + // if (result) { + // showSuccessToast('Unsubscribed', { position: 'bottom-right' }) + // } else { + // showErrorToast('Failed to unsubscribe', { position: 'bottom-right' }) + // } + // revalidate() + } + + return ( + + + + {confirmUnsubscribeName ? ( + { + await onUnsubscribe(confirmUnsubscribeName) + setConfirmUnsubscribeName(null) + }} + onOpenChange={() => setConfirmUnsubscribeName(null)} + /> + ) : null} + + + + Subscriptions + + + + {subscriptions + ? subscriptions.map((subscription, i) => { + return + }) + : null} + + + + ) +} From c3c65479bb15526609258636655d82dd2192da63 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Thu, 21 Apr 2022 15:18:51 +0800 Subject: [PATCH 4/9] add unsubscribe --- .../mutations/unsubscribeMutation.ts | 39 ++++++++++++ packages/web/pages/settings/subscriptions.tsx | 20 +++--- yarn.lock | 62 +++++++++++++++---- 3 files changed, 99 insertions(+), 22 deletions(-) create mode 100644 packages/web/lib/networking/mutations/unsubscribeMutation.ts diff --git a/packages/web/lib/networking/mutations/unsubscribeMutation.ts b/packages/web/lib/networking/mutations/unsubscribeMutation.ts new file mode 100644 index 000000000..da785a842 --- /dev/null +++ b/packages/web/lib/networking/mutations/unsubscribeMutation.ts @@ -0,0 +1,39 @@ +import { gql } from 'graphql-request' +import { gqlFetcher } from '../networkHelpers' +import { Subscription } from '@omnivore/api/src/generated/graphql' + +type UnsubscribeResult = { + unsubscribe: Unsubscribe + errorCodes?: unknown[] +} + +type Unsubscribe = { + subscription: Subscription +} + +export async function unsubscribeMutation( + subscribeName: string +): Promise { + const mutation = gql` + mutation { + unsubscribe(name: "${subscribeName}") { + ... on UnsubscribeSuccess { + subscription { + id + } + } + ... on UnsubscribeError { + errorCodes + } + } + } + ` + + try { + const data = (await gqlFetcher(mutation)) as UnsubscribeResult + return data.errorCodes ? undefined : data.unsubscribe.subscription.id + } catch (error) { + console.log('unsubscribeMutation error', error) + return undefined + } +} diff --git a/packages/web/pages/settings/subscriptions.tsx b/packages/web/pages/settings/subscriptions.tsx index 6c6db5e07..d6a4a93b9 100644 --- a/packages/web/pages/settings/subscriptions.tsx +++ b/packages/web/pages/settings/subscriptions.tsx @@ -7,6 +7,8 @@ import { applyStoredTheme } from '../../lib/themeUpdater' import { StyledText } from '../../components/elements/StyledText' import { ConfirmationModal } from '../../components/patterns/ConfirmationModal' import { useGetSubscriptionsQuery } from '../../lib/networking/queries/useGetSubscriptionsQuery' +import { unsubscribeMutation } from '../../lib/networking/mutations/unsubscribeMutation' +import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers' const HeaderWrapper = styled(Box, { width: '100%', @@ -20,14 +22,13 @@ export default function SubscriptionsPage(): JSX.Element { applyStoredTheme(false) async function onUnsubscribe(name: string): Promise { - // TODO: unsubscribe from the server - // const result = await deleteLabelMutation(name) - // if (result) { - // showSuccessToast('Unsubscribed', { position: 'bottom-right' }) - // } else { - // showErrorToast('Failed to unsubscribe', { position: 'bottom-right' }) - // } - // revalidate() + const result = await unsubscribeMutation(name) + if (result) { + showSuccessToast('Unsubscribed', { position: 'bottom-right' }) + } else { + showErrorToast('Failed to unsubscribe', { position: 'bottom-right' }) + } + revalidate() } return ( @@ -58,7 +59,7 @@ export default function SubscriptionsPage(): JSX.Element { - Subscriptions + Subscriptions @@ -68,7 +69,6 @@ export default function SubscriptionsPage(): JSX.Element { }) : null} - ) } diff --git a/yarn.lock b/yarn.lock index 5462e7404..c5a3eceeb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3053,7 +3053,7 @@ dependencies: "@istanbuljs/schema" "^0.1.2" -"@istanbuljs/schema@^0.1.2": +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": version "0.1.3" resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== @@ -3293,6 +3293,14 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" +"@jridgewell/trace-mapping@^0.3.7": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@lerna/add@4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@lerna/add/-/add-4.0.0.tgz#c36f57d132502a57b9e7058d1548b7a565ef183f" @@ -10202,6 +10210,27 @@ c8@^7.6.0: yargs "^16.2.0" yargs-parser "^20.2.9" +cacache@^12.0.2: + version "12.0.4" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c" + integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ== + dependencies: + bluebird "^3.5.5" + chownr "^1.1.1" + figgy-pudding "^3.5.1" + glob "^7.1.4" + graceful-fs "^4.1.15" + infer-owner "^1.0.3" + lru-cache "^5.1.1" + mississippi "^3.0.0" + mkdirp "^0.5.1" + move-concurrently "^1.0.1" + promise-inflight "^1.0.1" + rimraf "^2.6.3" + ssri "^6.0.1" + unique-filename "^1.1.1" + y18n "^4.0.0" + cacache@^15.0.5, cacache@^15.2.0: version "15.2.0" resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.2.0.tgz#73af75f77c58e72d8c630a7a2858cb18ef523389" @@ -11299,9 +11328,9 @@ core-js-compat@^3.20.2, core-js-compat@^3.21.0: semver "7.0.0" core-js-compat@^3.8.1: - version "3.22.2" - resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.22.2.tgz#eec621eb276518efcf718d0a6d9d042c3d0cad48" - integrity sha512-Fns9lU06ZJ07pdfmPMu7OnkIKGPKDzXKIiuGlSvHHapwqMUF2QnnsWwtueFZtSyZEilP0o6iUeHQwpn7LxtLUw== + version "3.22.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.22.1.tgz#47b9c5e79efbf13935f637449fa1cdec8cd9515f" + integrity sha512-CWbNqTluLMvZg1cjsQUbGiCM91dobSHKfDIyCoxuqxthdjGuUlaMbCsSehP3CBiVvG0C7P6UIrC1v0hgFE75jw== dependencies: browserslist "^4.20.2" semver "7.0.0" @@ -11312,14 +11341,14 @@ core-js-pure@^3.16.0: integrity sha512-oxKe64UH049mJqrKkynWp6Vu0Rlm/BTXO/bJZuN2mmR3RtOFNepLlSWDd1eo16PzHpQAoNG97rLU1V/YxesJjw== core-js-pure@^3.8.1, core-js-pure@^3.8.2: - version "3.22.2" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.22.2.tgz#c10bffdc3028d25c2aae505819a05543db61544f" - integrity sha512-Lb+/XT4WC4PaCWWtZpNPaXmjiNDUe5CJuUtbkMrIM1kb1T/jJoAIp+bkVP/r5lHzMr+ZAAF8XHp7+my6Ol0ysQ== + version "3.22.1" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.22.1.tgz#4d94e0c9a7b710da20dadd727fe98b43543119f0" + integrity sha512-TChjCtgcMDc8t12RiwAsThjqrS/VpBlEvDgL009ot4HESzBo3h2FSZNa6ZS1nWKZEPDoulnszxUll9n0/spflQ== core-js@^3.0.4, core-js@^3.6.5, core-js@^3.8.2: - version "3.22.2" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.22.2.tgz#3ea0a245b0895fa39d1faa15fe75d91ade504a01" - integrity sha512-Z5I2vzDnEIqO2YhELVMFcL1An2CIsFe9Q7byZhs8c/QxummxZlAHw33TUHbIte987LkisOgL0LwQ1P9D6VISnA== + version "3.22.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.22.1.tgz#1936e4f1da82675fe22ae10ee60ef638cd9752fd" + integrity sha512-l6CwCLq7XgITOQGhv1dIUmwCFoqFjyQ6zQHUCQlS0xKmb9d6OHIg8jDiEoswhaettT21BSF5qKr6kbvE+aKwxw== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" @@ -16158,7 +16187,7 @@ istanbul-lib-source-maps@^4.0.0: istanbul-lib-coverage "^3.0.0" source-map "^0.6.1" -istanbul-reports@^3.0.2, istanbul-reports@^3.1.3: +istanbul-reports@^3.0.2, istanbul-reports@^3.1.3, istanbul-reports@^3.1.4: version "3.1.4" resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.4.tgz#1b6f068ecbc6c331040aab5741991273e609e40c" integrity sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw== @@ -24279,6 +24308,15 @@ v8-to-istanbul@^8.1.0: convert-source-map "^1.6.0" source-map "^0.7.3" +v8-to-istanbul@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.0.0.tgz#be0dae58719fc53cb97e5c7ac1d7e6d4f5b19511" + integrity sha512-HcvgY/xaRm7isYmyx+lFKA4uQmfUbN0J4M0nNItvzTvH/iQ9kW5j/t4YSR+Ge323/lrgDAWJoF46tzGQHwBHFw== + dependencies: + "@jridgewell/trace-mapping" "^0.3.7" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + v8flags@^2.0.10: version "2.1.1" resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4" @@ -25137,7 +25175,7 @@ yargs-parser@^18.1.2, yargs-parser@^18.1.3: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^20.2.2, yargs-parser@^20.2.9: +yargs-parser@^20.2.2, yargs-parser@^20.2.3, yargs-parser@^20.2.9: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== From e6ca3c8eb4acf200f1b4c3e6f9c2624816337033 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Thu, 21 Apr 2022 12:03:52 -0700 Subject: [PATCH 5/9] Create subscription type instead of importing from GraphQL --- .../networking/mutations/unsubscribeMutation.ts | 2 +- .../queries/useGetSubscriptionsQuery.tsx | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/packages/web/lib/networking/mutations/unsubscribeMutation.ts b/packages/web/lib/networking/mutations/unsubscribeMutation.ts index da785a842..3eba481ab 100644 --- a/packages/web/lib/networking/mutations/unsubscribeMutation.ts +++ b/packages/web/lib/networking/mutations/unsubscribeMutation.ts @@ -1,6 +1,6 @@ import { gql } from 'graphql-request' import { gqlFetcher } from '../networkHelpers' -import { Subscription } from '@omnivore/api/src/generated/graphql' +import { Subscription } from '../queries/useGetSubscriptionsQuery' type UnsubscribeResult = { unsubscribe: Unsubscribe diff --git a/packages/web/lib/networking/queries/useGetSubscriptionsQuery.tsx b/packages/web/lib/networking/queries/useGetSubscriptionsQuery.tsx index 89d0d5528..357a8b697 100644 --- a/packages/web/lib/networking/queries/useGetSubscriptionsQuery.tsx +++ b/packages/web/lib/networking/queries/useGetSubscriptionsQuery.tsx @@ -1,7 +1,21 @@ import { gql } from 'graphql-request' import useSWR from 'swr' import { publicGqlFetcher } from '../networkHelpers' -import { Subscription } from '@omnivore/api/src/generated/graphql' + +export type SubscriptionStatus = 'ACTIVE' | 'DELETED' | 'UNSUBSCRIBED' + +export type Subscription = { + id: string + name: string + newsletterEmail: string + + url?: string + description?: string + + status: SubscriptionStatus + createdAt: Date + updatedAt: Date +}; type SubscriptionsQueryResponse = { isValidating: boolean From 38c190a08fd122d82066a373d0475d6f16146bdc Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Thu, 21 Apr 2022 12:13:55 -0700 Subject: [PATCH 6/9] Add debugging line --- packages/web/lib/networking/queries/useGetSubscriptionsQuery.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/web/lib/networking/queries/useGetSubscriptionsQuery.tsx b/packages/web/lib/networking/queries/useGetSubscriptionsQuery.tsx index 357a8b697..7598dcb4a 100644 --- a/packages/web/lib/networking/queries/useGetSubscriptionsQuery.tsx +++ b/packages/web/lib/networking/queries/useGetSubscriptionsQuery.tsx @@ -56,6 +56,7 @@ export function useGetSubscriptionsQuery(): SubscriptionsQueryResponse { ` const { data, mutate, error, isValidating } = useSWR(query, publicGqlFetcher) + console.log('subscriptions data', data) try { if (data) { From bcbda50daabd940bf2f39cc041991e3a376c834e Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Fri, 22 Apr 2022 16:05:07 +0800 Subject: [PATCH 7/9] Add basic UI for subscriptions --- .../api/src/resolvers/subscriptions/index.ts | 30 +-- ...082.do.add_default_subscription_status.sql | 10 + ...2.undo.add_default_subscription_status.sql | 9 + packages/web/components/elements/Table.tsx | 228 ++++++++++++++++++ .../queries/useGetSubscriptionsQuery.tsx | 5 +- packages/web/pages/settings/subscriptions.tsx | 70 +++--- 6 files changed, 295 insertions(+), 57 deletions(-) create mode 100755 packages/db/migrations/0082.do.add_default_subscription_status.sql create mode 100755 packages/db/migrations/0082.undo.add_default_subscription_status.sql create mode 100644 packages/web/components/elements/Table.tsx diff --git a/packages/api/src/resolvers/subscriptions/index.ts b/packages/api/src/resolvers/subscriptions/index.ts index 766eb6b0f..7936b25e5 100644 --- a/packages/api/src/resolvers/subscriptions/index.ts +++ b/packages/api/src/resolvers/subscriptions/index.ts @@ -37,25 +37,22 @@ export const subscriptionsResolver = authorized< try { const sortBy = sort?.by === SortBy.UpdatedTime ? 'updatedAt' : 'createdAt' const sortOrder = sort?.order === SortOrder.Ascending ? 'ASC' : 'DESC' - const user = await getRepository(User).findOne({ - where: { id: uid, subscriptions: { status: SubscriptionStatus.Active } }, - relations: { - subscriptions: true, - }, - order: { - subscriptions: { - [sortBy]: sortOrder, - }, - }, - }) + const user = await getRepository(User).findOneBy({ id: uid }) if (!user) { return { errorCodes: [SubscriptionsErrorCode.Unauthorized], } } + const subscriptions = await getRepository(Subscription).find({ + where: { user: { id: uid }, status: SubscriptionStatus.Active }, + order: { + [sortBy]: sortOrder, + }, + }) + return { - subscriptions: user.subscriptions || [], + subscriptions, } } catch (error) { log.error(error) @@ -73,9 +70,12 @@ export const unsubscribeResolver = authorized< log.info('unsubscribeResolver') try { - const subscription = await getRepository(Subscription).findOneBy({ - name, - user: { id: uid }, + const subscription = await getRepository(Subscription).findOne({ + where: { + name, + user: { id: uid }, + }, + relations: ['user'], }) if (!subscription) { return { diff --git a/packages/db/migrations/0082.do.add_default_subscription_status.sql b/packages/db/migrations/0082.do.add_default_subscription_status.sql new file mode 100755 index 000000000..8683078f9 --- /dev/null +++ b/packages/db/migrations/0082.do.add_default_subscription_status.sql @@ -0,0 +1,10 @@ +-- Type: DO +-- Name: add_default_subscription_status +-- Description: Add default value to subscription status field + +BEGIN; + +ALTER TABLE omnivore.subscriptions + ALTER COLUMN status SET DEFAULT 'ACTIVE'; + +COMMIT; diff --git a/packages/db/migrations/0082.undo.add_default_subscription_status.sql b/packages/db/migrations/0082.undo.add_default_subscription_status.sql new file mode 100755 index 000000000..c5c4771a3 --- /dev/null +++ b/packages/db/migrations/0082.undo.add_default_subscription_status.sql @@ -0,0 +1,9 @@ +-- Type: UNDO +-- Name: add_default_subscription_status +-- Description: Add default value to subscription status field + +BEGIN; + +ALTER TABLE omnivore.subscriptions ALTER status DROP DEFAULT; + +COMMIT; diff --git a/packages/web/components/elements/Table.tsx b/packages/web/components/elements/Table.tsx new file mode 100644 index 000000000..94945f1cf --- /dev/null +++ b/packages/web/components/elements/Table.tsx @@ -0,0 +1,228 @@ +import { Box, HStack, SpanBox, VStack } from './LayoutPrimitives' +import { styled } from '../tokens/stitches.config' +import { StyledText } from './StyledText' +import { InfoLink } from './InfoLink' +import { Button } from './Button' +import { Plus, Trash } from 'phosphor-react' +import { isDarkTheme } from '../../lib/themeUpdater' + +interface TableProps { + heading: string + infoLink?: string + onAdd?: () => void + headers: string[] + rows: string[][] + onDelete?: (id: string) => void +} + +const HeaderWrapper = styled(Box, { + width: '100%', + '@md': { + display: 'block', + }, +}) + +const TableCard = styled(Box, { + backgroundColor: '$grayBg', + display: 'flex', + alignItems: 'center', + padding: '10px 12px', + border: '0.5px solid $grayBgActive', + width: '100%', + + '&:hover': { + border: '0.5px solid #FFD234', + }, + '@md': { + paddingLeft: '0', + }, +}) + +const TableHeading = styled(Box, { + backgroundColor: '$grayBgActive', + border: '1px solid rgba(0, 0, 0, 0.06)', + display: 'none', + alignItems: 'center', + padding: '14px 0 14px 40px', + borderRadius: '5px 5px 0px 0px', + width: '100%', + '@md': { + display: 'flex', + }, +}) + +const Input = styled('input', { + backgroundColor: 'transparent', + color: '$grayTextContrast', + marginTop: '5px', + '&[disabled]': { + border: 'none', + }, +}) + +const IconButton = styled(Button, { + variants: { + style: { + ctaWhite: { + color: 'red', + padding: '10px', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + border: '1px solid $grayBorder', + boxSizing: 'border-box', + borderRadius: 6, + width: 40, + height: 40, + }, + }, + }, +}) + +export function Table(props: TableProps): JSX.Element { + const iconColor = isDarkTheme() ? '#D8D7D5' : '#5F5E58' + + return ( + + + + + {props.heading} + + {props.infoLink && } + {props.onAdd && ( + + )} + + + + {props.headers.map((header, index) => ( + + + {header} + + + ))} + + {props.rows.map((row, index) => ( + + + {row.map((cell, index) => ( + + + + ))} + {props.onDelete && ( + { + props.onDelete(row[0]) + }} + > + + + )} + + + ))} + + ) +} diff --git a/packages/web/lib/networking/queries/useGetSubscriptionsQuery.tsx b/packages/web/lib/networking/queries/useGetSubscriptionsQuery.tsx index 7598dcb4a..92ec8241a 100644 --- a/packages/web/lib/networking/queries/useGetSubscriptionsQuery.tsx +++ b/packages/web/lib/networking/queries/useGetSubscriptionsQuery.tsx @@ -15,7 +15,7 @@ export type Subscription = { status: SubscriptionStatus createdAt: Date updatedAt: Date -}; +} type SubscriptionsQueryResponse = { isValidating: boolean @@ -34,11 +34,12 @@ type SubscriptionsData = { export function useGetSubscriptionsQuery(): SubscriptionsQueryResponse { const query = gql` query GetSubscriptions { - subscriptions { + subscriptions(sort: { by: UPDATED_TIME }) { ... on SubscriptionsSuccess { subscriptions { id name + newsletterEmail url description status diff --git a/packages/web/pages/settings/subscriptions.tsx b/packages/web/pages/settings/subscriptions.tsx index d6a4a93b9..ec9d7cab5 100644 --- a/packages/web/pages/settings/subscriptions.tsx +++ b/packages/web/pages/settings/subscriptions.tsx @@ -1,23 +1,18 @@ import { useState } from 'react' import { PrimaryLayout } from '../../components/templates/PrimaryLayout' -import { styled } from '../../components/tokens/stitches.config' -import { Box, VStack } from '../../components/elements/LayoutPrimitives' import { Toaster } from 'react-hot-toast' import { applyStoredTheme } from '../../lib/themeUpdater' -import { StyledText } from '../../components/elements/StyledText' import { ConfirmationModal } from '../../components/patterns/ConfirmationModal' import { useGetSubscriptionsQuery } from '../../lib/networking/queries/useGetSubscriptionsQuery' import { unsubscribeMutation } from '../../lib/networking/mutations/unsubscribeMutation' import { showErrorToast, showSuccessToast } from '../../lib/toastHelpers' - -const HeaderWrapper = styled(Box, { - width: '100%', -}) +import { Table } from '../../components/elements/Table' export default function SubscriptionsPage(): JSX.Element { const { subscriptions, revalidate } = useGetSubscriptionsQuery() - const [confirmUnsubscribeName, setConfirmUnsubscribeName] = - useState(null) + const [confirmUnsubscribeName, setConfirmUnsubscribeName] = useState< + string | null + >(null) applyStoredTheme(false) @@ -31,6 +26,13 @@ export default function SubscriptionsPage(): JSX.Element { revalidate() } + const headers = ['Name', 'Email', 'Updated Time'] + const rows = subscriptions.map((subscription) => [ + subscription.name, + subscription.newsletterEmail, + subscription.updatedAt.toString(), + ]) + return ( - - {confirmUnsubscribeName ? ( - { - await onUnsubscribe(confirmUnsubscribeName) - setConfirmUnsubscribeName(null) - }} - onOpenChange={() => setConfirmUnsubscribeName(null)} - /> - ) : null} - - - - Subscriptions - - - - {subscriptions - ? subscriptions.map((subscription, i) => { - return - }) - : null} - + + {confirmUnsubscribeName ? ( + { + await onUnsubscribe(confirmUnsubscribeName) + setConfirmUnsubscribeName(null) + }} + onOpenChange={() => setConfirmUnsubscribeName(null)} + /> + ) : null} + ) } From fdd2d95164b6b89de0d390964bb69d306df2b298 Mon Sep 17 00:00:00 2001 From: Hongbo Wu Date: Fri, 22 Apr 2022 19:07:26 +0800 Subject: [PATCH 8/9] Add popular newsletters --- packages/web/components/elements/Table.tsx | 3 +- .../web/components/patterns/DropdownMenu.tsx | 39 ++++++++++++------- .../web/components/patterns/PrimaryHeader.tsx | 28 +++++++------ packages/web/pages/settings/subscriptions.tsx | 13 +++++++ 4 files changed, 58 insertions(+), 25 deletions(-) diff --git a/packages/web/components/elements/Table.tsx b/packages/web/components/elements/Table.tsx index 94945f1cf..38ba1587f 100644 --- a/packages/web/components/elements/Table.tsx +++ b/packages/web/components/elements/Table.tsx @@ -178,6 +178,7 @@ export function Table(props: TableProps): JSX.Element { borderBottomLeftRadius: index == props.rows.length - 1 ? '5px' : '', borderBottomRightRadius: index == props.rows.length - 1 ? '5px' : '', + padding: '10px 20px 10px 40px', }} > { - props.onDelete(row[0]) + props.onDelete && props.onDelete(row[0]) }} > diff --git a/packages/web/components/patterns/DropdownMenu.tsx b/packages/web/components/patterns/DropdownMenu.tsx index 317af1ade..074a32e22 100644 --- a/packages/web/components/patterns/DropdownMenu.tsx +++ b/packages/web/components/patterns/DropdownMenu.tsx @@ -1,9 +1,9 @@ import { ReactNode, useMemo, useState } from 'react' -import { Box, HStack, VStack } from './../elements/LayoutPrimitives' +import { HStack, VStack } from './../elements/LayoutPrimitives' import { Dropdown, - DropdownSeparator, DropdownOption, + DropdownSeparator, } from '../elements/DropdownElements' import { StyledText } from '../elements/StyledText' import { Button } from '../elements/Button' @@ -18,6 +18,7 @@ export type HeaderDropdownAction = | 'navigate-to-emails' | 'navigate-to-labels' | 'navigate-to-profile' + | 'navigate-to-subscriptions' | 'increaseFontSize' | 'decreaseFontSize' | 'logout' @@ -38,19 +39,27 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element { return ( - Theme + Theme - - @@ -67,6 +76,10 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element { onSelect={() => props.actionHandler('navigate-to-labels')} title="Labels" /> + props.actionHandler('navigate-to-subscriptions')} + title="Subscriptions" + /> window.Intercom('show')} title="Feedback" diff --git a/packages/web/components/patterns/PrimaryHeader.tsx b/packages/web/components/patterns/PrimaryHeader.tsx index 12c7e4754..79d7b8e4b 100644 --- a/packages/web/components/patterns/PrimaryHeader.tsx +++ b/packages/web/components/patterns/PrimaryHeader.tsx @@ -1,9 +1,9 @@ -import { Box, HStack, SpanBox } from '../elements/LayoutPrimitives' +import { Box, HStack } from '../elements/LayoutPrimitives' import { OmnivoreNameLogo } from './../elements/images/OmnivoreNameLogo' import { DropdownMenu, HeaderDropdownAction } from './../patterns/DropdownMenu' -import { updateTheme } from '../../lib/themeUpdater' +import { darkenTheme, lightenTheme, updateTheme } from '../../lib/themeUpdater' import { AvatarDropdown } from './../elements/AvatarDropdown' -import { theme, ThemeId } from './../tokens/stitches.config' +import { ThemeId } from './../tokens/stitches.config' import { useCallback, useEffect, useState } from 'react' import { ScrollOffsetChangeset, @@ -12,7 +12,6 @@ import { import { useRouter } from 'next/router' import { useKeyboardShortcuts } from '../../lib/keyboardShortcuts/useKeyboardShortcuts' import { primaryCommands } from '../../lib/keyboardShortcuts/navigationShortcuts' -import { darkenTheme, lightenTheme } from '../../lib/themeUpdater' import { UserBasicData } from '../../lib/networking/queries/useGetViewerQuery' import { setupAnalytics } from '../../lib/analytics' import { Button } from '../elements/Button' @@ -111,6 +110,9 @@ export function PrimaryHeader(props: HeaderProps): JSX.Element { router.push(`/${props.user.profile.username}`) } break + case 'navigate-to-subscriptions': + router.push('/settings/subscriptions') + break case 'logout': props.setShowLogoutConfirmation(true) break @@ -187,13 +189,17 @@ function NavHeader(props: NavHeaderProps): JSX.Element { {props.toolbarControl && ( - {props.toolbarControl} diff --git a/packages/web/pages/settings/subscriptions.tsx b/packages/web/pages/settings/subscriptions.tsx index ec9d7cab5..fba1a7b43 100644 --- a/packages/web/pages/settings/subscriptions.tsx +++ b/packages/web/pages/settings/subscriptions.tsx @@ -59,6 +59,19 @@ export default function SubscriptionsPage(): JSX.Element { rows={rows} onDelete={setConfirmUnsubscribeName} /> + + {/* TODO: Dynamically loaded from API response */} +
) } From 51c3c5506c23bf486d64c839b8816a871d9bf745 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 22 Apr 2022 12:08:41 -0700 Subject: [PATCH 9/9] Comment out subscriptions dropdown option so we can deploy without exposing to users --- packages/web/components/patterns/DropdownMenu.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/web/components/patterns/DropdownMenu.tsx b/packages/web/components/patterns/DropdownMenu.tsx index 074a32e22..973047a68 100644 --- a/packages/web/components/patterns/DropdownMenu.tsx +++ b/packages/web/components/patterns/DropdownMenu.tsx @@ -76,10 +76,10 @@ export function DropdownMenu(props: DropdownMenuProps): JSX.Element { onSelect={() => props.actionHandler('navigate-to-labels')} title="Labels" /> - props.actionHandler('navigate-to-subscriptions')} title="Subscriptions" - /> + /> */} window.Intercom('show')} title="Feedback"