From 0a317a549a471e8191f8d0db9e64bb7663377159 Mon Sep 17 00:00:00 2001 From: jamesqquick Date: Wed, 6 Mar 2024 10:29:39 -0600 Subject: [PATCH 001/127] perf: skip loading state for button submission --- src/components/forms/SubscribeFormButton.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/forms/SubscribeFormButton.tsx b/src/components/forms/SubscribeFormButton.tsx index 252456d..74ad54d 100644 --- a/src/components/forms/SubscribeFormButton.tsx +++ b/src/components/forms/SubscribeFormButton.tsx @@ -1,7 +1,8 @@ import { useFormStatus } from 'react-dom' export default function SubscribeFormButton() { - const { pending } = useFormStatus() + // const { pending } = useFormStatus() + const pending = false return ( ) From 5075e196aa4f07c04bb195c5086a55abd11fa6cd Mon Sep 17 00:00:00 2001 From: jamesqquick Date: Thu, 7 Mar 2024 12:18:39 -0600 Subject: [PATCH 002/127] test: random comment --- src/components/forms/SubscribeFormButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/forms/SubscribeFormButton.tsx b/src/components/forms/SubscribeFormButton.tsx index 74ad54d..77a7fe1 100644 --- a/src/components/forms/SubscribeFormButton.tsx +++ b/src/components/forms/SubscribeFormButton.tsx @@ -1,7 +1,7 @@ import { useFormStatus } from 'react-dom' export default function SubscribeFormButton() { - // const { pending } = useFormStatus() + //TODO: update const { pending } = useFormStatus() const pending = false return ( From da4f1ca04e047e1fde9a980b466ce0dd5e1acbd8 Mon Sep 17 00:00:00 2001 From: jamesqquick Date: Thu, 7 Mar 2024 12:19:36 -0600 Subject: [PATCH 003/127] test: random comment --- src/components/forms/SubscribeFormButton.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/forms/SubscribeFormButton.tsx b/src/components/forms/SubscribeFormButton.tsx index 77a7fe1..a394416 100644 --- a/src/components/forms/SubscribeFormButton.tsx +++ b/src/components/forms/SubscribeFormButton.tsx @@ -1,7 +1,7 @@ import { useFormStatus } from 'react-dom' export default function SubscribeFormButton() { - //TODO: update const { pending } = useFormStatus() + //update const { pending } = useFormStatus() const pending = false return ( From fbeb0e7002f11fcaa4d5617612b840c244e425e3 Mon Sep 17 00:00:00 2001 From: jamesqquick Date: Fri, 8 Mar 2024 13:18:05 -0600 Subject: [PATCH 004/127] test: adding sentry inp --- package-lock.json | 176 ++++++++++++++++++++-------------------- package.json | 2 +- sentry.client.config.ts | 5 +- sentry.server.config.ts | 4 +- 4 files changed, 94 insertions(+), 93 deletions(-) diff --git a/package-lock.json b/package-lock.json index bdb0c3b..bcdfefb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@react-email/components": "^0.0.15", "@react-email/render": "^0.0.12", "@react-email/tailwind": "^0.0.14", - "@sentry/nextjs": "^7.102.1", + "@sentry/nextjs": "^7.106.0", "@tanstack/react-table": "^8.12.0", "@vercel/analytics": "^1.2.2", "@vercel/speed-insights": "^1.0.10", @@ -3215,57 +3215,57 @@ } }, "node_modules/@sentry-internal/feedback": { - "version": "7.102.1", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.102.1.tgz", - "integrity": "sha512-vY4hpLLMNLjICtWiizc7KeGbWOTUMGrF7C+9dPCztZww3CLgzWy9A7DvPj5hodRiYzpdRnAMl8yQnMFbYXh7bA==", + "version": "7.106.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-7.106.0.tgz", + "integrity": "sha512-Uz6pv3SN8XORTMme5xPxP/kuho7CAA6E/pMlpMjsojjBbnwLIICu10JaEZNsF/AtEya1RcNVTyPCrtF1F3sBYA==", "dependencies": { - "@sentry/core": "7.102.1", - "@sentry/types": "7.102.1", - "@sentry/utils": "7.102.1" + "@sentry/core": "7.106.0", + "@sentry/types": "7.106.0", + "@sentry/utils": "7.106.0" }, "engines": { "node": ">=12" } }, "node_modules/@sentry-internal/replay-canvas": { - "version": "7.102.1", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-7.102.1.tgz", - "integrity": "sha512-GUX4RWI10uRjdjeyvCLtAAhWRVqnAnG6+yNxWfqUQ3qMA7B7XxG43KT2UhSnulmErNzODQ6hA68rGPwwYeRIww==", + "version": "7.106.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-7.106.0.tgz", + "integrity": "sha512-59qmT6XqbwpQuK1nVmv+XFxgd80gpYNH3aqgF5BEKux23kRB02/ARR5MwYyIHgVO0JhwdGIuiTfiLVNDu+nwTQ==", "dependencies": { - "@sentry/core": "7.102.1", - "@sentry/replay": "7.102.1", - "@sentry/types": "7.102.1", - "@sentry/utils": "7.102.1" + "@sentry/core": "7.106.0", + "@sentry/replay": "7.106.0", + "@sentry/types": "7.106.0", + "@sentry/utils": "7.106.0" }, "engines": { "node": ">=12" } }, "node_modules/@sentry-internal/tracing": { - "version": "7.102.1", - "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.102.1.tgz", - "integrity": "sha512-RkFlFyAC0fQOvBbBqnq0CLmFW5m3JJz9pKbZd5vXPraWAlniKSb1bC/4DF9SlNx0FN1LWG+IU3ISdpzwwTeAGg==", + "version": "7.106.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.106.0.tgz", + "integrity": "sha512-O8Es6Sa/tP80nfl+8soNfWzeRNFcT484SvjLR8BS3pHM9KDAlwNXyoQhFr2BKNYL1irbq6UF6eku4xCnUKVmqA==", "dependencies": { - "@sentry/core": "7.102.1", - "@sentry/types": "7.102.1", - "@sentry/utils": "7.102.1" + "@sentry/core": "7.106.0", + "@sentry/types": "7.106.0", + "@sentry/utils": "7.106.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/browser": { - "version": "7.102.1", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.102.1.tgz", - "integrity": "sha512-7BOfPBiM7Kp6q/iy0JIbsBTxIASV+zWXByqqjuEMWGj3X2u4oRIfm3gv4erPU/l+CORQUVQZLSPGoIoM1gbB/A==", + "version": "7.106.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.106.0.tgz", + "integrity": "sha512-OrHdw44giTtMa1DmlIUMBN4ypj1xTES9DLjq16ufK+bLqW3rWzwCuTy0sb9ZmSxc7fL2pdBlsL+sECiS+U2TEw==", "dependencies": { - "@sentry-internal/feedback": "7.102.1", - "@sentry-internal/replay-canvas": "7.102.1", - "@sentry-internal/tracing": "7.102.1", - "@sentry/core": "7.102.1", - "@sentry/replay": "7.102.1", - "@sentry/types": "7.102.1", - "@sentry/utils": "7.102.1" + "@sentry-internal/feedback": "7.106.0", + "@sentry-internal/replay-canvas": "7.106.0", + "@sentry-internal/tracing": "7.106.0", + "@sentry/core": "7.106.0", + "@sentry/replay": "7.106.0", + "@sentry/types": "7.106.0", + "@sentry/utils": "7.106.0" }, "engines": { "node": ">=8" @@ -3292,25 +3292,25 @@ } }, "node_modules/@sentry/core": { - "version": "7.102.1", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.102.1.tgz", - "integrity": "sha512-QjY+LSP3du3J/C8x/FfEbRxgZgsWd0jfTJ4P7s9f219I1csK4OeBMC3UA1HwEa0pY/9OF6H/egW2CjOcMM5Pdg==", + "version": "7.106.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.106.0.tgz", + "integrity": "sha512-Dc13XtnyFaXup2E4vCbzuG0QKAVjrJBk4qfGwvSJaTuopEaEWBs2MpK6hRzFhsz9S3T0La7c1F/62NptvTUWsQ==", "dependencies": { - "@sentry/types": "7.102.1", - "@sentry/utils": "7.102.1" + "@sentry/types": "7.106.0", + "@sentry/utils": "7.106.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/integrations": { - "version": "7.102.1", - "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-7.102.1.tgz", - "integrity": "sha512-Its3Ru6xCAqpaLE3cTxW/b91js2SIFoXa8LWtQDJ7tmTdwPAbT8Pij1F4bOhhaqLYbjLtCXGl/NR2cffsiRLww==", + "version": "7.106.0", + "resolved": "https://registry.npmjs.org/@sentry/integrations/-/integrations-7.106.0.tgz", + "integrity": "sha512-vvd2pO5S55Zpr1y4TL5KgyqIF94S4h4E9OezQoE0wlbzSd3EDIWblHcfKoBJUBMUxxgfE1dQ84BlSmfVlI7t6Q==", "dependencies": { - "@sentry/core": "7.102.1", - "@sentry/types": "7.102.1", - "@sentry/utils": "7.102.1", + "@sentry/core": "7.106.0", + "@sentry/types": "7.106.0", + "@sentry/utils": "7.106.0", "localforage": "^1.8.1" }, "engines": { @@ -3318,18 +3318,18 @@ } }, "node_modules/@sentry/nextjs": { - "version": "7.102.1", - "resolved": "https://registry.npmjs.org/@sentry/nextjs/-/nextjs-7.102.1.tgz", - "integrity": "sha512-gOI/GD7DWhc3WucyYnepl8Nu5jmpa1YfR6jWDzTkPE2CV9zKK9zulTdqk+Aig9ch62SAmJOGkgejm5k9PE2XzQ==", + "version": "7.106.0", + "resolved": "https://registry.npmjs.org/@sentry/nextjs/-/nextjs-7.106.0.tgz", + "integrity": "sha512-Z4XEIr0MMou8Em4OMcXtHnCHY0/sMj4TV32Js8A8oMvWZSmxUMC7vgDkjw6F0ss7p7inMmqoTBscumTIWApobg==", "dependencies": { "@rollup/plugin-commonjs": "24.0.0", - "@sentry/core": "7.102.1", - "@sentry/integrations": "7.102.1", - "@sentry/node": "7.102.1", - "@sentry/react": "7.102.1", - "@sentry/types": "7.102.1", - "@sentry/utils": "7.102.1", - "@sentry/vercel-edge": "7.102.1", + "@sentry/core": "7.106.0", + "@sentry/integrations": "7.106.0", + "@sentry/node": "7.106.0", + "@sentry/react": "7.106.0", + "@sentry/types": "7.106.0", + "@sentry/utils": "7.106.0", + "@sentry/vercel-edge": "7.106.0", "@sentry/webpack-plugin": "1.21.0", "chalk": "3.0.0", "resolve": "1.22.8", @@ -3351,28 +3351,28 @@ } }, "node_modules/@sentry/node": { - "version": "7.102.1", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.102.1.tgz", - "integrity": "sha512-mb3vmM3SGuCruckPiv/Vafeh89UQavTfpPFoU6Jwe6dSpQ39BO8fO8k8Zev+/nP6r/FKLtX17mJobErHECXsYw==", + "version": "7.106.0", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.106.0.tgz", + "integrity": "sha512-4DIqbu5K7//lK/k2nV8lqKeGQzhu2T1OpJFmiUrjN6fUKWivGFjZrcmQDS7tvhAAyJezkL3LlrNU4tjPHUElPA==", "dependencies": { - "@sentry-internal/tracing": "7.102.1", - "@sentry/core": "7.102.1", - "@sentry/types": "7.102.1", - "@sentry/utils": "7.102.1" + "@sentry-internal/tracing": "7.106.0", + "@sentry/core": "7.106.0", + "@sentry/types": "7.106.0", + "@sentry/utils": "7.106.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/react": { - "version": "7.102.1", - "resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.102.1.tgz", - "integrity": "sha512-X4j2DgbktlEifnd21YJKCayAmff5hnaS+9MNz9OonEwD0ARi0ks7bo0wtWHMjPK20992MO+JwczVg/1BXJYDdQ==", - "dependencies": { - "@sentry/browser": "7.102.1", - "@sentry/core": "7.102.1", - "@sentry/types": "7.102.1", - "@sentry/utils": "7.102.1", + "version": "7.106.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.106.0.tgz", + "integrity": "sha512-5KMfvkBYqK990o8Ju9vsRRRR0F8TnPpZynC9YqsJYpCViKjIt8W/ysDLrU1Dj5XZyeVElZjdRlXB0aQYrwEUWg==", + "dependencies": { + "@sentry/browser": "7.106.0", + "@sentry/core": "7.106.0", + "@sentry/types": "7.106.0", + "@sentry/utils": "7.106.0", "hoist-non-react-statics": "^3.3.2" }, "engines": { @@ -3383,47 +3383,47 @@ } }, "node_modules/@sentry/replay": { - "version": "7.102.1", - "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.102.1.tgz", - "integrity": "sha512-HR/j9dGIvbrId8fh8mQlODx7JrhRmawEd9e9P3laPtogWCg/5TI+XPb2VGSaXOX9VWtb/6Z2UjHsaGjgg6YcuA==", + "version": "7.106.0", + "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.106.0.tgz", + "integrity": "sha512-buaAOvOI+3pFm+76vwtxSxciBATHyR78aDjStghJZcIpFDNF31K8ZV0uP9+EUPbXHohtkTwZ86cn/P9cyY6NgA==", "dependencies": { - "@sentry-internal/tracing": "7.102.1", - "@sentry/core": "7.102.1", - "@sentry/types": "7.102.1", - "@sentry/utils": "7.102.1" + "@sentry-internal/tracing": "7.106.0", + "@sentry/core": "7.106.0", + "@sentry/types": "7.106.0", + "@sentry/utils": "7.106.0" }, "engines": { "node": ">=12" } }, "node_modules/@sentry/types": { - "version": "7.102.1", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.102.1.tgz", - "integrity": "sha512-htKorf3t/D0XYtM7foTcmG+rM47rDP6XdbvCcX5gBCuCYlzpM1vqCt2rl3FLktZC6TaIpFRJw1TLfx6m+x5jdA==", + "version": "7.106.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.106.0.tgz", + "integrity": "sha512-oKTkDaL6P9xJC5/zHLRemHTWboUqRYjkJNaZCN63j4kJqGy56wee4vDtDese/NWWn4U4C1QV1h+Mifm2HmDcQg==", "engines": { "node": ">=8" } }, "node_modules/@sentry/utils": { - "version": "7.102.1", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.102.1.tgz", - "integrity": "sha512-+8WcFjHVV/HROXSAwMuUzveElBFC43EiTG7SNEBNgOUeQzQVTmbUZXyTVgLrUmtoWqvnIxCacoLxtZo1o67kdg==", + "version": "7.106.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.106.0.tgz", + "integrity": "sha512-bVsePsXLpFu/1sH4rpJrPcnVxW2fXXfGfGxKs6Bm+dkOMbuVTlk/KAzIbdjCDIpVlrMDJmMNEv5xgTFjgWDkjw==", "dependencies": { - "@sentry/types": "7.102.1" + "@sentry/types": "7.106.0" }, "engines": { "node": ">=8" } }, "node_modules/@sentry/vercel-edge": { - "version": "7.102.1", - "resolved": "https://registry.npmjs.org/@sentry/vercel-edge/-/vercel-edge-7.102.1.tgz", - "integrity": "sha512-iB6KCSxrvO172VjfQHGiYpyXPKNbx6Cz01GA1YDByRiUSgSpAq0qAFRY4lvd736w5KX4s9hen+XmZ7Gmqj8Pag==", - "dependencies": { - "@sentry-internal/tracing": "7.102.1", - "@sentry/core": "7.102.1", - "@sentry/types": "7.102.1", - "@sentry/utils": "7.102.1" + "version": "7.106.0", + "resolved": "https://registry.npmjs.org/@sentry/vercel-edge/-/vercel-edge-7.106.0.tgz", + "integrity": "sha512-+A2L7IXVcTOC8esqMVyW4IL+ceeCrCgzxKPBtzseEU9klE6JEXufuGTPeTYw4z/Yfk9mrjxvEDHWXTZ2yhvDJw==", + "dependencies": { + "@sentry-internal/tracing": "7.106.0", + "@sentry/core": "7.106.0", + "@sentry/types": "7.106.0", + "@sentry/utils": "7.106.0" }, "engines": { "node": ">=8" diff --git a/package.json b/package.json index b7084a0..dca5485 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "@react-email/components": "^0.0.15", "@react-email/render": "^0.0.12", "@react-email/tailwind": "^0.0.14", - "@sentry/nextjs": "^7.104.0", + "@sentry/nextjs": "^7.106.0", "@tanstack/react-table": "^8.12.0", "@vercel/analytics": "^1.2.2", "@vercel/speed-insights": "^1.0.10", diff --git a/sentry.client.config.ts b/sentry.client.config.ts index c006b55..f8f7625 100644 --- a/sentry.client.config.ts +++ b/sentry.client.config.ts @@ -2,7 +2,7 @@ // The config you add here will be used whenever a users loads a page in their browser. // https://docs.sentry.io/platforms/javascript/guides/nextjs/ -import * as Sentry from "@sentry/nextjs"; +import * as Sentry from '@sentry/nextjs' Sentry.init({ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, @@ -26,5 +26,6 @@ Sentry.init({ maskAllText: true, blockAllMedia: true, }), + Sentry.browserTracingIntegration({ enableInp: true }), ], -}); +}) diff --git a/sentry.server.config.ts b/sentry.server.config.ts index 86d85e1..3d3d91a 100644 --- a/sentry.server.config.ts +++ b/sentry.server.config.ts @@ -2,7 +2,7 @@ // The config you add here will be used whenever the server handles a request. // https://docs.sentry.io/platforms/javascript/guides/nextjs/ -import * as Sentry from "@sentry/nextjs"; +import * as Sentry from '@sentry/nextjs' Sentry.init({ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, @@ -12,4 +12,4 @@ Sentry.init({ // Setting this option to true will print useful information to the console while you're setting up Sentry. debug: false, -}); +}) From 4ce174beeca08adc7d7939da70cb89e4e5d5a3f3 Mon Sep 17 00:00:00 2001 From: jamesqquick Date: Fri, 8 Mar 2024 13:51:23 -0600 Subject: [PATCH 005/127] test: adding sentry inp --- sentry.client.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry.client.config.ts b/sentry.client.config.ts index f8f7625..4dd8c59 100644 --- a/sentry.client.config.ts +++ b/sentry.client.config.ts @@ -22,7 +22,7 @@ Sentry.init({ // You can remove this option if you're not planning to use the Sentry Session Replay feature: integrations: [ new Sentry.Replay({ - // Additional Replay configuration goes in here, for example: + // Additional Replay configuration goes in here: maskAllText: true, blockAllMedia: true, }), From 920ddf41ac406c128fd7a21d0129fd2e5e1190bc Mon Sep 17 00:00:00 2001 From: Christine Belzie <105683440+CBID2@users.noreply.github.com> Date: Mon, 11 Mar 2024 18:43:21 -0400 Subject: [PATCH 006/127] feat: add template for pull requests --- .github/pull_request_template.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..18c0e48 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,15 @@ + +## Issue + + +## Changes Proposed + + + +## Screenshots + + + +## Note to reviewers + + From d5ceec2a695673389318724c8b115f4eccb181d4 Mon Sep 17 00:00:00 2001 From: Christine Belzie Date: Mon, 11 Mar 2024 18:48:06 -0400 Subject: [PATCH 007/127] fix: revise template Signed-off-by: Christine Belzie --- .github/pull_request_template.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 18c0e48..eca98a0 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,11 +1,9 @@ +## Description + -## Issue +## Related Ticket(s) and Documentation(s) -## Changes Proposed - - - ## Screenshots From 42b6fdc65f3a6138af02c87e863b17206084051c Mon Sep 17 00:00:00 2001 From: jamesqquick Date: Wed, 13 Mar 2024 09:40:42 -0500 Subject: [PATCH 008/127] fix: added public environment variable --- .frontmatter/database/taxonomyDb.json | 1 + frontmatter.json | 3 + package-lock.json | 120 ++++++++++++++++++++++++++ sentry.client.config.ts | 2 +- sentry.edge.config.ts | 2 +- sentry.server.config.ts | 2 +- src/types/env.ts | 3 + 7 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 .frontmatter/database/taxonomyDb.json create mode 100644 frontmatter.json diff --git a/.frontmatter/database/taxonomyDb.json b/.frontmatter/database/taxonomyDb.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/.frontmatter/database/taxonomyDb.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/frontmatter.json b/frontmatter.json new file mode 100644 index 0000000..561c5b4 --- /dev/null +++ b/frontmatter.json @@ -0,0 +1,3 @@ +{ + "$schema": "https://frontmatter.codes/frontmatter.schema.json" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c842b51..6d3fbcb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11703,6 +11703,126 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.0.tgz", + "integrity": "sha512-1jgudN5haWxiAl3O1ljUS2GfupPmcftu2RYJqZiMJmmbBT5M1XDffjUtRUzP4W3cBHsrvkfOFdQ71hAreNQP6g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.0.tgz", + "integrity": "sha512-RHo7Tcj+jllXUbK7xk2NyIDod3YcCPDZxj1WLIYxd709BQ7WuRYl3OWUNG+WUfqeQBds6kvZYlc42NJJTNi4tQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.0.tgz", + "integrity": "sha512-v6kP8sHYxjO8RwHmWMJSq7VZP2nYCkRVQ0qolh2l6xroe9QjbgV8siTbduED4u0hlk0+tjS6/Tuy4n5XCp+l6g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.0.tgz", + "integrity": "sha512-zJ2pnoFYB1F4vmEVlb/eSe+VH679zT1VdXlZKX+pE66grOgjmKJHKacf82g/sWE4MQ4Rk2FMBCRnX+l6/TVYzQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.0.tgz", + "integrity": "sha512-rbaIYFt2X9YZBSbH/CwGAjbBG2/MrACCVu2X0+kSykHzHnYH5FjHxwXLkcoJ10cX0aWCEynpu+rP76x0914atg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.0.tgz", + "integrity": "sha512-o1N5TsYc8f/HpGt39OUQpQ9AKIGApd3QLueu7hXk//2xq5Z9OxmV6sQfNp8C7qYmiOlHYODOGqNNa0e9jvchGQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.0.tgz", + "integrity": "sha512-XXIuB1DBRCFwNO6EEzCTMHT5pauwaSj4SWs7CYnME57eaReAKBXCnkUE80p/pAZcewm7hs+vGvNqDPacEXHVkw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.0.tgz", + "integrity": "sha512-9WEbVRRAqJ3YFVqEZIxUqkiO8l1nool1LmNxygr5HWF8AcSYsEpneUDhmjUVJEzO2A04+oPtZdombzzPPkTtgg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } } } } diff --git a/sentry.client.config.ts b/sentry.client.config.ts index 071773b..9925acc 100644 --- a/sentry.client.config.ts +++ b/sentry.client.config.ts @@ -6,7 +6,7 @@ import * as Sentry from '@sentry/nextjs' Sentry.init({ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, - environment: process.env.VERCEL_ENV, + environment: process.env.NEXT_PUBLIC_ENVIRONMENT, // Adjust this value in production, or use tracesSampler for greater control tracesSampleRate: 1, diff --git a/sentry.edge.config.ts b/sentry.edge.config.ts index c6322f2..14d4efb 100644 --- a/sentry.edge.config.ts +++ b/sentry.edge.config.ts @@ -7,7 +7,7 @@ import * as Sentry from '@sentry/nextjs' Sentry.init({ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, - environment: process.env.VERCEL_ENV, + environment: process.env.NEXT_PUBLIC_ENVIRONMENT, // Adjust this value in production, or use tracesSampler for greater control tracesSampleRate: 1, // Setting this option to true will print useful information to the console while you're setting up Sentry. diff --git a/sentry.server.config.ts b/sentry.server.config.ts index a3de36d..c3e9480 100644 --- a/sentry.server.config.ts +++ b/sentry.server.config.ts @@ -6,7 +6,7 @@ import * as Sentry from '@sentry/nextjs' Sentry.init({ dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, - environment: process.env.VERCEL_ENV, + environment: process.env.NEXT_PUBLIC_ENVIRONMENT, // Adjust this value in production, or use tracesSampler for greater control tracesSampleRate: 1, diff --git a/src/types/env.ts b/src/types/env.ts index f12bba3..316baa7 100644 --- a/src/types/env.ts +++ b/src/types/env.ts @@ -18,6 +18,7 @@ const envVariables = z.object({ NEXT_PUBLIC_BASE_URL: z.string().url(), NEXT_PUBLIC_POSTHOG_KEY: z.string().optional(), NEXT_PUBLIC_POSTHOG_HOST: z.string().optional(), + NEXT_PUBLIC_ENVIRONMENT: z.string(), }) const { @@ -36,6 +37,7 @@ const { FROM_EMAIL, REPLY_TO_EMAIL, NEXT_PUBLIC_BASE_URL, + NEXT_PUBLIC_ENVIRONMENT, } = process.env const envServerSchema = envVariables.safeParse({ @@ -54,6 +56,7 @@ const envServerSchema = envVariables.safeParse({ FROM_EMAIL, REPLY_TO_EMAIL, NEXT_PUBLIC_BASE_URL, + NEXT_PUBLIC_ENVIRONMENT, }) if (!envServerSchema.success) { From deeabf153a6b42b8a3c48a79ecf07e4308d3f91e Mon Sep 17 00:00:00 2001 From: jamesqquick Date: Wed, 13 Mar 2024 10:00:42 -0500 Subject: [PATCH 009/127] fix: add loader to subscribe button --- src/components/forms/SubscribeFormButton.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/forms/SubscribeFormButton.tsx b/src/components/forms/SubscribeFormButton.tsx index a394416..d3d6cd2 100644 --- a/src/components/forms/SubscribeFormButton.tsx +++ b/src/components/forms/SubscribeFormButton.tsx @@ -1,8 +1,7 @@ import { useFormStatus } from 'react-dom' export default function SubscribeFormButton() { - //update const { pending } = useFormStatus() - const pending = false + const { pending } = useFormStatus() return ( */} + {/* user logout */} {isUserAuthenticated && ( From 1e7b2c3bbe4f8402eeefa2d5cd50f4d0447aa829 Mon Sep 17 00:00:00 2001 From: Chris Nowicki <102450568+chris-nowicki@users.noreply.github.com> Date: Mon, 18 Mar 2024 22:23:23 -0400 Subject: [PATCH 021/127] feat: add back never miss a deal section --- src/app/page.tsx | 2 ++ src/components/NeverMissADeal.tsx | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index ce08e57..c8e080e 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,9 +1,11 @@ import Hero from '@/components/Hero' +import NeverMissADeal from '@/components/NeverMissADeal' export default function Home() { return (
+
) } diff --git a/src/components/NeverMissADeal.tsx b/src/components/NeverMissADeal.tsx index aae0f0b..c178fd7 100644 --- a/src/components/NeverMissADeal.tsx +++ b/src/components/NeverMissADeal.tsx @@ -3,7 +3,7 @@ import SubscribeForm from './forms/SubscribeForm' export default function NeverMissADeal() { return ( -
+
Never miss a deal for your favorite tools or courses From bc26673b0f7b80b3084af28360e2625f3e632b1d Mon Sep 17 00:00:00 2001 From: Chris Nowicki <102450568+chris-nowicki@users.noreply.github.com> Date: Mon, 18 Mar 2024 22:32:53 -0400 Subject: [PATCH 022/127] feat: add in separator with proper spacings --- src/app/page.tsx | 5 +++++ src/components/Hero.tsx | 24 ++++++++++++------------ src/components/NeverMissADeal.tsx | 2 +- 3 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index c8e080e..594209d 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,11 +1,16 @@ +import DevGiveaways from '@/components/DevGiveaways' import Hero from '@/components/Hero' import NeverMissADeal from '@/components/NeverMissADeal' +import Separator from '@/components/Separator' export default function Home() { return (
+ + +
) } diff --git a/src/components/Hero.tsx b/src/components/Hero.tsx index 2e87dd2..4b5c7e5 100644 --- a/src/components/Hero.tsx +++ b/src/components/Hero.tsx @@ -4,19 +4,19 @@ export default function Hero() { return (
-
-

- The best deals and{' '} - giveaways for developers -

+
+

+ The best deals and{' '} + giveaways for developers +

- {/* subscription form */} -
-

- Get upcoming and ongoing deals sent straight to your inbox every - month! -

- + {/* subscription form */} +
+

+ Get upcoming and ongoing deals sent straight to your inbox every + month! +

+
diff --git a/src/components/NeverMissADeal.tsx b/src/components/NeverMissADeal.tsx index c178fd7..0d54ec7 100644 --- a/src/components/NeverMissADeal.tsx +++ b/src/components/NeverMissADeal.tsx @@ -3,7 +3,7 @@ import SubscribeForm from './forms/SubscribeForm' export default function NeverMissADeal() { return ( -
+
Never miss a deal for your favorite tools or courses From 03ee18df7851c5e037ff3d5c4a50738293608fec Mon Sep 17 00:00:00 2001 From: jamesqquick Date: Tue, 19 Mar 2024 12:53:59 -0500 Subject: [PATCH 023/127] fix: updated footer typescript type for alt property --- src/components/Footer.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 60c211d..118a98b 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -7,6 +7,7 @@ type TechStack = { id: string size: number className?: string + alt: string } const techStack: TechStack[] = [ From 3f54c70b92ae881c0fbb2fe518897c68f330e9a4 Mon Sep 17 00:00:00 2001 From: jamesqquick Date: Tue, 19 Mar 2024 12:55:26 -0500 Subject: [PATCH 024/127] fix: return string error from subscribe --- src/actions/subscriber-subscribe.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/actions/subscriber-subscribe.ts b/src/actions/subscriber-subscribe.ts index 5520df9..f72c058 100644 --- a/src/actions/subscriber-subscribe.ts +++ b/src/actions/subscriber-subscribe.ts @@ -15,7 +15,7 @@ export const subscribe = async (formData: FormData) => { // parse the email const parsed = subscribeSchema.safeParse({ email: formData.get('email') }) - if (!parsed.success) return { error: parsed.error } + if (!parsed.success) return { error: parsed.error.message } const checkedEmail = parsed.data.email.toLowerCase() const existingSubscriber = await getOneSubscriberByEmail(checkedEmail) From f4cc709464f7da50160d70f939d2035a4d354d51 Mon Sep 17 00:00:00 2001 From: Ryan Furrer Date: Tue, 19 Mar 2024 21:53:40 -0400 Subject: [PATCH 025/127] chore: update meta description to current website header --- src/app/layout.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index e8266b0..47b6977 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -22,8 +22,7 @@ const raleway = Raleway({ export const metadata: Metadata = { title: 'Developer Deals', metadataBase: new URL('https://dealsfordevs.com/'), - description: - 'The Best Black Friday deals on courses, tools, and desk setups for developers!', + description: 'The best deals and giveaways for developers!', openGraph: { images: [ { From 3c6308ac09ca92e71c3ba5827802f071e382ee66 Mon Sep 17 00:00:00 2001 From: Bryan Date: Wed, 20 Mar 2024 14:34:19 -0400 Subject: [PATCH 026/127] docs: updated contributing.md --- .github/contributing.md | 174 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 .github/contributing.md diff --git a/.github/contributing.md b/.github/contributing.md new file mode 100644 index 0000000..9cd3ca8 --- /dev/null +++ b/.github/contributing.md @@ -0,0 +1,174 @@ +# Deals for Devs Contribution Guide + +Thank you for expressing interest to contribute to the Deals for Devs repo! The intention with this document is to give every individual the information needed to confidently work on the Deals for Dev repo and ensure that each contribution follows the same standard to allow consistent updates from the community. + +_Note:_ any pull request created for an issue that already has someone else assigned **will be closed without review**. + +## Table of Contents +- [Deals for Devs Contribution Guide](#deals-for-devs-contribution-guide) + - [Table of Contents](#table-of-contents) + - [Label Meanings](#label-meanings) + - [Status Labels](#status-labels) + - [When to Contribute](#when-to-contribute) + - [Check Before Doing Anything](#check-before-doing-anything) + - [Being Assigned an Issue](#being-assigned-an-issue) + - [Creating an Issue](#creating-an-issue) + - [Setting Up Your Local Clone](#setting-up-your-local-clone) + - [Working on an Issue](#working-on-an-issue) + - [Opening a Pull Request](#opening-a-pull-request) + - [Further Help](#further-help) + +## Label Meanings + +The labels that get applied to issues and PRs in our repos have specific meanings and are broken into two categories: status and type. An issue/PR should only ever have one status label, but can have multiple type labels. The following isn't a complete list, but rather a list of the labels that are more universal across all of our repos. + +### Status Labels +* **Ally**: Code is not up to WCAG standards +* **Bug**: Something isn't working +* **Documentation**: Improvements or additions to documentation +* **Duplicate**: This issue or pull request already exists +* **Enhancement**: Suggesting improvements to existing features +* **Feature**: Requests for new features +* **Good first issue**: Good for newcomers +* **Help Wanted**: Extra attention is needed +* **High Priority**: This issue/PR is crucial +* **Medium Priority**: This issue/PR is could lead to a High Priority +* **Low Priority**: This issue/PR does not need immediate attention +* **Maintenance**: Codebase maintenance tasks like refactoring or dependency updates +* **Questions**: Further information is requested +* **Won't fix**: This will not be worked on + +## When to Contribute + +**No Issue is too Small**: We understand that some people may be skeptical to contribute to an open source project. It can be frightening to step into a project that is already five or six years in it's life span, or just beginning. However there is one thing to always remember, "no issue is too small"! Whether it be a typo or a major bug fix any help is always grately appreciated it! Plus as you get more comfortable with the open source work flow you will begin to want to contribute to more complex tasks! + +### Check Before Doing Anything + +It's important that you look through any open [issues](https://github.com/Learn-Build-Teach/deals-for-devs/issues) or [pull requests](https://github.com/Learn-Build-Teach/deals-for-devs/pulls) in our repo before attempting to submit a new issue or work on a change, regardless of the complexity. This will help avoid any duplicates from being made, as well as prevent more than one person working on the same thing at the same time. + +If your issue already exists in an open issue or PR, but you feel there are details missing, comment on the existing issue/PR to let those involved know of those missing details. + +### Being Assigned an Issue + +If you would like to work on a specific issue within our repo: + +1) Find an issue that is not currently assigned to anyone. + * A couple of good places to start are issues with the `Status: Help Wanted` or `Type: Good First Issue` labels. You can filter the issues list to only show ones with these (or any) specific labels to make them easier to find. + +2) Ask to be assigned the issue by a maintainer. + * **If you are not a maintainer, do not give others permission to work on an issue** + +3) **Wait to be assigned the issue before starting any work**. + +4) After being assigned, address each item listed in the acceptance criteria, if any exist. + * If an issue doesn't have any acceptance criteria, feel free to go about resolving the issue however you wish. You can also ask the maintainer that assigned you the issue if there are any specific acceptance criteria. + +### Creating an Issue + +1. If you would like to make a simple change that is not part of an existing issue, you are welcome to skip the next step and just submit a PR with your proposed change(s). + +2. Create a new issue and **read the issue template in its entirety and fill out all applicable sections**. If you aren't sure how to create an issue, you can read the GitHub documentation on [creating an issue from a repository](https://docs.github.com/en/issues/tracking-your-work-with-issues/creating-an-issue#creating-an-issue-from-a-repository). + * The title of the issue must follow the format described in the issue template. + * If you would like to be assigned the issue you are creating, complete the applicable checkbox in the issue template. Note that this does not guarantee that you will be assigned the issue, but rather it lets maintainers know that you are interested. + * The more information you are able to provide in your issue, the better. + +### Setting Up Your Local Clone + +**Important:** +Before you begin working on an issue please follow these steps to setup your working environment: + +1. Fork the repo to your own GitHub account. If you don't know how to do so, follow the GitHub documentation on how to [fork a repo](https://docs.github.com/en/get-started/quickstart/fork-a-repo). + +2. Clone the forked repo to your local machine with one of the commands below. Be sure the `` text is replaced with your actual GitHub username, and the `` with the actual repo name. You can also read the GitHub documentation on [cloning a repository](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository). + + ```bash + # If you have SSH set up with Git: + git clone git@github.com:/.git + # Otherwise for HTTPS: + git clone https://github.com//.git + + # An example: + git clone git@github.com:BryanF1nes/deals-for-devs.git + ``` + +3. `cd` into the directory of your local clone, then set the upstream remote so you can keep your local clone synced with Deals for Devs original repo. The `` below should be the same as the one you used when creating your local clone in the previous step. + + ```bash + # If you have SSH set up with Git: + git remote add upstream git@github.com:/.git + # Otherwise for HTTPS: + git remote add upstream https://github.com//.git + + # An example: + git remote add upstream git@github.com:Learn-Build-Teach/deals-for-devs.git + ``` + +### Working on an Issue + +Once you have the repo forked and cloned, the upstream remote has been set, and you are in the `dev` branch you can begin working on your issue: + +1. Create a new branch off of the `dev` branch (unless given specific instructions), replacing the `` with an actual branch name that briefly explains the purpose of the branch in some way: + + ```bash + git checkout -b + + # Some examples: + git checkout -b fix_details_page + git checkout -b fix_main_page_bug + git checkout -b feat_details_page + ``` + +2. Add commits as you work on your issue using [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary), replacing the `` text with your actual commit message: + + ```bash + git commit -m "" + + # An example: + git commit -m "docs: corrected spelling of FEAUTRE" + ``` + +3. Make sure to sync your work with the remote `dev` branch unless the maintainer has given specific instructions: + + ```bash + # Fetching the most updated copy from dev branch + git fetch upstream dev + + # Merging the copy to your local main + git merge + ``` + +4. Push your branch to your forked repo, replacing the `` with the branch you've been working on locally: + + ```bash + git push origin + + # An example: + git push origin feat_details_page + ``` + +### Opening a Pull Request + +1. After pushing your changes, go to your forked repo on GitHub and click the "Compare & pull request" button. If you have multiples of this button, be sure you click the one for the correct branch. + +> PRs should be made to the `dev` branch. + + * If you don't see this button, you can click the branch dropdown menu and then select the branch you just pushed from your local clone: + + ![GitHub branch dropdown menu](https://user-images.githubusercontent.com/70952936/150646139-bc080c64-db57-4776-8db1-6525b7b47be2.jpg) + + * Once you have switched to the correct branch on GitHub, click the "Contribute" dropdown and then click the "Open pull request" button. + +2. **Read the PR template in its entirety before filling it out and submitting a PR**. Not filling out the template correctly will delay a PR getting merged. + * If a checkbox is not required and is not applicable to your PR, do not complete it. + * The title of the PR must follow the format described in the PR template. + * If the PR is meant to close an open issue, you must link that issue in Step 1 of the PR template. This can be done either by replacing the `XXXXX` with the issue number, e.g. `Closes #2013`, or if the issue is in another TOP repo replacing the `#XXXXX` with the URL of the issue, e.g. `Closes https://github.com/Learn-Build-Teach/deals-for-devs/issues/XXXX`. This streamlines the issue closing process, as an issue that is linked to a PR will be closed when that PR gets merged. + * If the PR is not part of an open issue, be sure to describe the reason(s) for the change(s) in more detail in Step 1 of the PR template, as well as outlining the changes made in the PR in Step 2. + +3. At this point a maintainer will either leave general comments, request changes, or approve and merge your PR. + * It is important to respond to any comments or requested changes in a timely manner, otherwise your PR may be closed without being merged due to inactivity. + * After pushing any requested changes to the branch you opened the PR with, be sure to re-request a review from the maintainer that requested those changes at the top of the right sidebar (this will only appear when a maintainer is assigned as a reviewer or has requested changes): + + ![Reviewers section of GitHub's sidebar](https://user-images.githubusercontent.com/70952936/150647064-4fdd59d1-82a4-4f18-894d-0e43a5ee0ffb.jpg) + +## Further Help +Please let us know if you require any further help with any of the steps in this guide. If you need help with creating an issue [GitHub has a guide on how to do so](https://docs.github.com/en/issues/tracking-your-work-with-issues/creating-an-issue). For questions not covered by this guide you can also join our [Discord](https://discord.com/invite/vM2bagU). \ No newline at end of file From 63b88f2e9fe27675e35a2cddc87dfa99a2e3a707 Mon Sep 17 00:00:00 2001 From: Ryan Furrer Date: Thu, 21 Mar 2024 00:15:08 -0400 Subject: [PATCH 027/127] refactor: split admin dashboard, manage deals, and manage subs to their own pages --- src/app/(admin)/dashboard/deals/page.tsx | 44 ++++++++++++++++++ src/app/(admin)/dashboard/page.tsx | 4 +- .../(admin)/dashboard/subscribers/page.tsx | 45 +++++++++++++++++++ src/app/globals.css | 10 +++++ src/components/dashboard/Dashboard.tsx | 35 +++++++-------- src/components/deals/DealsList.tsx | 4 +- src/components/subscriber/SubscriberList.tsx | 12 ++--- 7 files changed, 124 insertions(+), 30 deletions(-) create mode 100644 src/app/(admin)/dashboard/deals/page.tsx create mode 100644 src/app/(admin)/dashboard/subscribers/page.tsx diff --git a/src/app/(admin)/dashboard/deals/page.tsx b/src/app/(admin)/dashboard/deals/page.tsx new file mode 100644 index 0000000..a31a7ab --- /dev/null +++ b/src/app/(admin)/dashboard/deals/page.tsx @@ -0,0 +1,44 @@ +import { auth } from '@clerk/nextjs' +import { redirect } from 'next/navigation' +import { isAdminUser } from '@/utils/auth' +import Dashboard from '@/components/dashboard/Dashboard' +import { getAllDeals, getAllSubscribers } from '@/lib/queries' +import DealsList from '@/components/deals/DealsList' + +export default async function Home() { + const { userId } = auth() + + if (!userId) { + return redirect('/') + } + + // If user is logged in check if user is an admin + const isAdmin = await isAdminUser(userId) + + if (!isAdmin) { + return redirect('/') + } + + // fetch all deals and subscribers + const dealsData = getAllDeals() + const subscribersData = getAllSubscribers() + + const [deals, subscribers] = await Promise.all([dealsData, subscribersData]) + + const subscriberList = JSON.parse(JSON.stringify(subscribers)) + const dealsList = JSON.parse(JSON.stringify(deals)) + + return ( +
+

Manage Deals

+
+
+ +
+
+ +
+
+
+ ) +} diff --git a/src/app/(admin)/dashboard/page.tsx b/src/app/(admin)/dashboard/page.tsx index bfe882b..18e1070 100644 --- a/src/app/(admin)/dashboard/page.tsx +++ b/src/app/(admin)/dashboard/page.tsx @@ -28,8 +28,8 @@ export default async function Home() { const dealsList = JSON.parse(JSON.stringify(deals)) return ( -
- +
+
) } diff --git a/src/app/(admin)/dashboard/subscribers/page.tsx b/src/app/(admin)/dashboard/subscribers/page.tsx new file mode 100644 index 0000000..ada6673 --- /dev/null +++ b/src/app/(admin)/dashboard/subscribers/page.tsx @@ -0,0 +1,45 @@ +import { auth } from '@clerk/nextjs' +import { redirect } from 'next/navigation' +import { isAdminUser } from '@/utils/auth' +import Dashboard from '@/components/dashboard/Dashboard' +import { getAllDeals, getAllSubscribers } from '@/lib/queries' +import SubscriberList from '@/components/subscriber/SubscriberList' + +export default async function Home() { + const { userId } = auth() + + if (!userId) { + return redirect('/') + } + + // If user is logged in check if user is an admin + const isAdmin = await isAdminUser(userId) + + if (!isAdmin) { + return redirect('/') + } + + // fetch all deals and subscribers + const dealsData = getAllDeals() + const subscribersData = getAllSubscribers() + + const [deals, subscribers] = await Promise.all([dealsData, subscribersData]) + + const subscriberList = JSON.parse(JSON.stringify(subscribers)) + const dealsList = JSON.parse(JSON.stringify(deals)) + + return ( +
+

Manage Subscribers

+ +
+
+ +
+
+ +
+
+
+ ) +} diff --git a/src/app/globals.css b/src/app/globals.css index 6219d73..1651bce 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -2,6 +2,12 @@ @tailwind components; @tailwind utilities; +@layer base { + * { + @apply min-w-0; + } +} + .custom-checkbox { display: inline-block; position: relative; @@ -25,3 +31,7 @@ color: black; border: 1px solid #14b8a6; } + +.debug { + @apply outline outline-red-500; +} diff --git a/src/components/dashboard/Dashboard.tsx b/src/components/dashboard/Dashboard.tsx index 59385db..3140c84 100644 --- a/src/components/dashboard/Dashboard.tsx +++ b/src/components/dashboard/Dashboard.tsx @@ -1,11 +1,12 @@ 'use client' import { useState } from 'react' -import type { Subscribers, DealsRecord } from '@/xata' -import DealsList from '../deals/DealsList' -import SubscriberList from '@/components/subscriber/SubscriberList' +// import type { Subscribers, DealsRecord } from '@/xata' +// import DealsList from '../deals/DealsList' +// import SubscriberList from '@/components/subscriber/SubscriberList' +import Link from 'next/link' interface DashboardProps { - deals: any + deals?: any subscribers?: any } @@ -13,32 +14,26 @@ export default function Dashboard({ deals, subscribers }: DashboardProps) { const [selectedTab, setSelectedTab] = useState('deals') return ( -
- {/* header */} -
-

+
+
+

Admin Dashboard

-
- - +
- - {selectedTab === 'deals' && } - {selectedTab === 'subscribers' && ( - - )}
) } diff --git a/src/components/deals/DealsList.tsx b/src/components/deals/DealsList.tsx index 256912e..4f5920e 100644 --- a/src/components/deals/DealsList.tsx +++ b/src/components/deals/DealsList.tsx @@ -10,9 +10,9 @@ export default function DealsList({ isAdmin?: boolean }) { return ( -
+
{deals.length > 0 && ( -
+
{deals.map((deal) => ( ))} diff --git a/src/components/subscriber/SubscriberList.tsx b/src/components/subscriber/SubscriberList.tsx index cafbecc..af4dae5 100644 --- a/src/components/subscriber/SubscriberList.tsx +++ b/src/components/subscriber/SubscriberList.tsx @@ -8,16 +8,16 @@ interface SubscribersProps { export default function SubscriberList({ subscribers }: SubscribersProps) { return ( -
-

Add a Subscriber

+
+

Add a Subscriber

-
-

Subscriber List

-
    +
    +

    Subscriber List

    +
      {subscribers.map((subscriber) => (
    • {subscriber.email} From 8458af5f05fbe78f9327b93a944dd665274ca17b Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Thu, 21 Mar 2024 12:44:32 -0400 Subject: [PATCH 028/127] docs: add BryanF1nes as a contributor for doc (#87) * docs: update README.md [skip ci] * docs: update .all-contributorsrc [skip ci] --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com> Co-authored-by: James Q Quick --- .all-contributorsrc | 9 +++++++++ README.md | 4 +--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 994b26e..f9ef154 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -53,6 +53,15 @@ "doc" ] }, + { + "login": "BryanF1nes", + "name": "Bryan Fines", + "avatar_url": "https://avatars.githubusercontent.com/u/49371751?v=4", + "profile": "https://github.com/BryanF1nes", + "contributions": [ + "doc" + ] + }, { "login": "elliezub", "name": "Ellie", diff --git a/README.md b/README.md index 3c56f2e..2d9d3dd 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,6 @@ # Deals for Devs - [![All Contributors](https://img.shields.io/badge/all_contributors-6-orange.svg?style=flat-square)](#contributors-) - - **The best deals and giveaways for developers** @@ -67,6 +64,7 @@ Discord](https://www.learnbuildteach.com/) by messaging James (@jamesqquick) on Waseem Medhat
      Waseem Medhat

      💻 klae32
      klae32

      💻 Edwin Boon
      Edwin Boon

      💻 + Bryan Fines
      Bryan Fines

      📖 Ellie
      Ellie

      💻 Christine Belzie
      Christine Belzie

      💻 📖 ️️️️♿️ From 73e2cb44ba91493d5efe4bf63797189e4e1d893d Mon Sep 17 00:00:00 2001 From: Bryan Fines <49371751+BryanF1nes@users.noreply.github.com> Date: Thu, 21 Mar 2024 14:19:02 -0400 Subject: [PATCH 029/127] docs: updated and re-structured pull_request_template (#86) * docs: updated and re-structured pull_request_template * docs: updated comments per last PR request --- .github/pull_request_template.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index eca98a0..1dafb82 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,13 +1,20 @@ + + ## Description - + -## Related Ticket(s) and Documentation(s) - +## Issue + -## Screenshots +## Screenshot + - +## PR Requirements + +- [ ] I have carefully read through and understand the [Deals-for-Devs Contributing Guide](https://github.com/Learn-Build-Teach/deals-for-devs/blob/dev/.github/contributing.md) +- [ ] The title of this PR follows the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) format +- [ ] The `Description` gives a good representation of the changes made +- [ ] If this PR addresses an open Issue, it is linked in the `Issue` section -## Note to reviewers - + \ No newline at end of file From b17339640d947346fa97f74ba620acd1aa124abc Mon Sep 17 00:00:00 2001 From: jamesqquick Date: Thu, 21 Mar 2024 13:20:58 -0500 Subject: [PATCH 030/127] feat: migrating queries to prisma --- package-lock.json | 314 ++++++++++++------- package.json | 2 + prisma/schema.prisma | 51 +++ src/actions/deals.ts | 15 +- src/actions/subscriber-subscribe.ts | 2 + src/app/(admin)/dashboard/page.tsx | 15 +- src/app/api/cron/monthly-newsletter/route.ts | 61 ++-- src/app/api/deals/approve/route.ts | 41 +-- src/app/api/deals/reject/route.ts | 40 +-- src/app/api/deals/route.tsx | 51 ++- src/app/deals/[category]/page.tsx | 16 +- src/app/deals/page.tsx | 6 +- src/components/deals/Deal.tsx | 5 +- src/components/deals/DealsList.tsx | 7 +- src/components/deals/FeaturedDeals.tsx | 11 +- src/lib/db.ts | 15 + src/lib/queries.ts | 168 +++++++--- src/types/Types.ts | 7 +- src/types/env.ts | 3 + src/utils/auth.ts | 11 +- 20 files changed, 537 insertions(+), 304 deletions(-) create mode 100644 prisma/schema.prisma create mode 100644 src/lib/db.ts diff --git a/package-lock.json b/package-lock.json index 6d3fbcb..5aca672 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "dependencies": { "@clerk/nextjs": "^4.29.8", + "@prisma/client": "^5.11.0", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-select": "^2.0.0", @@ -28,6 +29,7 @@ "lucide-react": "^0.340.0", "next": "14.1.0", "posthog-js": "^1.108.3", + "prisma": "^5.11.0", "react": "^18", "react-countdown": "^2.3.5", "react-day-picker": "^8.10.0", @@ -1127,6 +1129,126 @@ "node": ">= 10" } }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.0.tgz", + "integrity": "sha512-1jgudN5haWxiAl3O1ljUS2GfupPmcftu2RYJqZiMJmmbBT5M1XDffjUtRUzP4W3cBHsrvkfOFdQ71hAreNQP6g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.0.tgz", + "integrity": "sha512-RHo7Tcj+jllXUbK7xk2NyIDod3YcCPDZxj1WLIYxd709BQ7WuRYl3OWUNG+WUfqeQBds6kvZYlc42NJJTNi4tQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.0.tgz", + "integrity": "sha512-v6kP8sHYxjO8RwHmWMJSq7VZP2nYCkRVQ0qolh2l6xroe9QjbgV8siTbduED4u0hlk0+tjS6/Tuy4n5XCp+l6g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.0.tgz", + "integrity": "sha512-zJ2pnoFYB1F4vmEVlb/eSe+VH679zT1VdXlZKX+pE66grOgjmKJHKacf82g/sWE4MQ4Rk2FMBCRnX+l6/TVYzQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.0.tgz", + "integrity": "sha512-rbaIYFt2X9YZBSbH/CwGAjbBG2/MrACCVu2X0+kSykHzHnYH5FjHxwXLkcoJ10cX0aWCEynpu+rP76x0914atg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.0.tgz", + "integrity": "sha512-o1N5TsYc8f/HpGt39OUQpQ9AKIGApd3QLueu7hXk//2xq5Z9OxmV6sQfNp8C7qYmiOlHYODOGqNNa0e9jvchGQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.0.tgz", + "integrity": "sha512-XXIuB1DBRCFwNO6EEzCTMHT5pauwaSj4SWs7CYnME57eaReAKBXCnkUE80p/pAZcewm7hs+vGvNqDPacEXHVkw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.0.tgz", + "integrity": "sha512-9WEbVRRAqJ3YFVqEZIxUqkiO8l1nool1LmNxygr5HWF8AcSYsEpneUDhmjUVJEzO2A04+oPtZdombzzPPkTtgg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1214,6 +1336,63 @@ "node": ">=14" } }, + "node_modules/@prisma/client": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.11.0.tgz", + "integrity": "sha512-SWshvS5FDXvgJKM/a0y9nDC1rqd7KG0Q6ZVzd+U7ZXK5soe73DJxJJgbNBt2GNXOa+ysWB4suTpdK5zfFPhwiw==", + "hasInstallScript": true, + "engines": { + "node": ">=16.13" + }, + "peerDependencies": { + "prisma": "*" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + } + } + }, + "node_modules/@prisma/debug": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.11.0.tgz", + "integrity": "sha512-N6yYr3AbQqaiUg+OgjkdPp3KPW1vMTAgtKX6+BiB/qB2i1TjLYCrweKcUjzOoRM5BriA4idrkTej9A9QqTfl3A==" + }, + "node_modules/@prisma/engines": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.11.0.tgz", + "integrity": "sha512-gbrpQoBTYWXDRqD+iTYMirDlF9MMlQdxskQXbhARhG6A/uFQjB7DZMYocMQLoiZXO/IskfDOZpPoZE8TBQKtEw==", + "hasInstallScript": true, + "dependencies": { + "@prisma/debug": "5.11.0", + "@prisma/engines-version": "5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102", + "@prisma/fetch-engine": "5.11.0", + "@prisma/get-platform": "5.11.0" + } + }, + "node_modules/@prisma/engines-version": { + "version": "5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102.tgz", + "integrity": "sha512-WXCuyoymvrS4zLz4wQagSsc3/nE6CHy8znyiMv8RKazKymOMd5o9FP5RGwGHAtgoxd+aB/BWqxuP/Ckfu7/3MA==" + }, + "node_modules/@prisma/fetch-engine": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.11.0.tgz", + "integrity": "sha512-994viazmHTJ1ymzvWugXod7dZ42T2ROeFuH6zHPcUfp/69+6cl5r9u3NFb6bW8lLdNjwLYEVPeu3hWzxpZeC0w==", + "dependencies": { + "@prisma/debug": "5.11.0", + "@prisma/engines-version": "5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102", + "@prisma/get-platform": "5.11.0" + } + }, + "node_modules/@prisma/get-platform": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.11.0.tgz", + "integrity": "sha512-rxtHpMLxNTHxqWuGOLzR2QOyQi79rK1u1XYAVLZxDGTLz/A+uoDnjz9veBFlicrpWjwuieM4N6jcnjj/DDoidw==", + "dependencies": { + "@prisma/debug": "5.11.0" + } + }, "node_modules/@radix-ui/colors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/colors/-/colors-1.0.1.tgz", @@ -8851,6 +9030,21 @@ "node": ">=6" } }, + "node_modules/prisma": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.11.0.tgz", + "integrity": "sha512-KCLiug2cs0Je7kGkQBN9jDWoZ90ogE/kvZTUTgz2h94FEo8pczCkPH7fPNXkD1sGU7Yh65risGGD1HQ5DF3r3g==", + "hasInstallScript": true, + "dependencies": { + "@prisma/engines": "5.11.0" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=16.13" + } + }, "node_modules/prismjs": { "version": "1.29.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", @@ -11703,126 +11897,6 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.1.0.tgz", - "integrity": "sha512-1jgudN5haWxiAl3O1ljUS2GfupPmcftu2RYJqZiMJmmbBT5M1XDffjUtRUzP4W3cBHsrvkfOFdQ71hAreNQP6g==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.1.0.tgz", - "integrity": "sha512-RHo7Tcj+jllXUbK7xk2NyIDod3YcCPDZxj1WLIYxd709BQ7WuRYl3OWUNG+WUfqeQBds6kvZYlc42NJJTNi4tQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.1.0.tgz", - "integrity": "sha512-v6kP8sHYxjO8RwHmWMJSq7VZP2nYCkRVQ0qolh2l6xroe9QjbgV8siTbduED4u0hlk0+tjS6/Tuy4n5XCp+l6g==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.1.0.tgz", - "integrity": "sha512-zJ2pnoFYB1F4vmEVlb/eSe+VH679zT1VdXlZKX+pE66grOgjmKJHKacf82g/sWE4MQ4Rk2FMBCRnX+l6/TVYzQ==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.1.0.tgz", - "integrity": "sha512-rbaIYFt2X9YZBSbH/CwGAjbBG2/MrACCVu2X0+kSykHzHnYH5FjHxwXLkcoJ10cX0aWCEynpu+rP76x0914atg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.1.0.tgz", - "integrity": "sha512-o1N5TsYc8f/HpGt39OUQpQ9AKIGApd3QLueu7hXk//2xq5Z9OxmV6sQfNp8C7qYmiOlHYODOGqNNa0e9jvchGQ==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.1.0.tgz", - "integrity": "sha512-XXIuB1DBRCFwNO6EEzCTMHT5pauwaSj4SWs7CYnME57eaReAKBXCnkUE80p/pAZcewm7hs+vGvNqDPacEXHVkw==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.1.0.tgz", - "integrity": "sha512-9WEbVRRAqJ3YFVqEZIxUqkiO8l1nool1LmNxygr5HWF8AcSYsEpneUDhmjUVJEzO2A04+oPtZdombzzPPkTtgg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } } } } diff --git a/package.json b/package.json index 8655738..71102ba 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@clerk/nextjs": "^4.29.8", + "@prisma/client": "^5.11.0", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-select": "^2.0.0", @@ -31,6 +32,7 @@ "lucide-react": "^0.340.0", "next": "14.1.0", "posthog-js": "^1.108.3", + "prisma": "^5.11.0", "react": "^18", "react-countdown": "^2.3.5", "react-day-picker": "^8.10.0", diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..b01e973 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,51 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model Deal { + coupon String? @db.VarChar(50) + approved Boolean @default(false) + description String @db.VarChar(255) + couponPercent Int? @default(0) + featured Boolean @default(false) + name String @db.VarChar(50) + link String @db.VarChar(255) + category String @db.VarChar(20) + startDate DateTime @default(now()) + endDate DateTime @default(now()) + email String @db.VarChar(50) + xata_id String @unique(map: "Deal__pgroll_new_xata_id_key") @default(dbgenerated("('rec_'::text || (xata_private.xid())::text)")) + xata_version Int @default(0) + xata_createdat DateTime @default(now()) @db.Timestamptz(6) + xata_updatedat DateTime @default(now()) @db.Timestamptz(6) +} + +model AdminUser { + userId String @id @default(uuid()) + xata_id String @unique(map: "AdminUser__pgroll_new_xata_id_key") @default(dbgenerated("('rec_'::text || (xata_private.xid())::text)")) + xata_version Int @default(0) + xata_createdat DateTime @default(now()) @db.Timestamptz(6) + xata_updatedat DateTime @default(now()) @db.Timestamptz(6) +} + +model Subscriber { + status String @db.VarChar(20) + email String @db.VarChar(50) + verified Boolean @default(false) + token String @db.VarChar(50) + conferenceNotifications Boolean @default(false) + ebookNotifications Boolean @default(false) + courseNotifications Boolean @default(false) + toolNotifications Boolean @default(false) + officeEquipmentNotifications Boolean @default(false) + miscNotifications Boolean @default(false) + xata_version Int @default(0) + xata_createdat DateTime @default(now()) @db.Timestamptz(6) + xata_updatedat DateTime @default(now()) @db.Timestamptz(6) + xata_id String @unique(map: "Subscriber__pgroll_new_xata_id_key") @default(dbgenerated("('rec_'::text || (xata_private.xid())::text)")) +} diff --git a/src/actions/deals.ts b/src/actions/deals.ts index 4234570..de41cb2 100644 --- a/src/actions/deals.ts +++ b/src/actions/deals.ts @@ -3,6 +3,7 @@ import { DealsRecord, getXataClient } from '@/xata' import { redirect } from 'next/navigation' import { z } from 'zod' +import prisma from '@/lib/db' const dealSchema = z.object({ name: z.string(), @@ -12,7 +13,7 @@ const dealSchema = z.object({ endDate: z.coerce.date(), coupon: z.string().optional(), couponPercent: z.number().optional(), - email: z.string().email().optional(), + email: z.string().email(), //TODO: don't replicate array category: z.enum(['misc', 'ebook', 'video', 'tool', 'conference']), }) @@ -45,17 +46,11 @@ export async function createDeal(formData: FormData) { couponPercent: parsed.data.couponPercent, email: parsed.data.email, category: parsed.data.category, - image: { - name: parsed.data.name, - mediaType: 'image/png', - base64Content: '', - }, } - const xataClient = getXataClient() - const createdRecord = await xataClient.db.deals.create(newDeal, [ - 'image.uploadUrl', - ]) + const createdRecord = await prisma.deal.create({ + data: newDeal, + }) console.log(createdRecord) redirect(`/thank-you`) diff --git a/src/actions/subscriber-subscribe.ts b/src/actions/subscriber-subscribe.ts index f72c058..b360a7f 100644 --- a/src/actions/subscriber-subscribe.ts +++ b/src/actions/subscriber-subscribe.ts @@ -4,6 +4,7 @@ import { z } from 'zod' import { sendConfirmationEmail } from '../utils/resend/email-sendConfirmation' import { createValidateEmailLink, createConfirmEmailLink } from '@/lib/utils' import { createSubscriber, getOneSubscriberByEmail } from '@/lib/queries' +import { Status } from '@/types/Types' const subscribeSchema = z.object({ email: z.string().email(), @@ -35,6 +36,7 @@ export const subscribe = async (formData: FormData) => { officeEquipmentNotifications: true, toolNotifications: true, conferenceNotifications: true, + status: Status.UNSUBSCRIBED, } // add new subscriber to the database diff --git a/src/app/(admin)/dashboard/page.tsx b/src/app/(admin)/dashboard/page.tsx index bfe882b..9bd0623 100644 --- a/src/app/(admin)/dashboard/page.tsx +++ b/src/app/(admin)/dashboard/page.tsx @@ -6,7 +6,6 @@ import { getAllDeals, getAllSubscribers } from '@/lib/queries' export default async function Home() { const { userId } = auth() - if (!userId) { return redirect('/') } @@ -19,17 +18,19 @@ export default async function Home() { } // fetch all deals and subscribers - const dealsData = getAllDeals() - const subscribersData = getAllSubscribers() + const dealsPromise = getAllDeals() + const subscribersPromise = getAllSubscribers() - const [deals, subscribers] = await Promise.all([dealsData, subscribersData]) + const [deals, subscribers] = await Promise.all([ + dealsPromise, + subscribersPromise, + ]) - const subscriberList = JSON.parse(JSON.stringify(subscribers)) - const dealsList = JSON.parse(JSON.stringify(deals)) + console.log(subscribers) return (
      - +
      ) } diff --git a/src/app/api/cron/monthly-newsletter/route.ts b/src/app/api/cron/monthly-newsletter/route.ts index cad4660..a56e80d 100644 --- a/src/app/api/cron/monthly-newsletter/route.ts +++ b/src/app/api/cron/monthly-newsletter/route.ts @@ -2,44 +2,45 @@ import { NextRequest, NextResponse } from 'next/server' import { subDays } from 'date-fns' import { getXataClient } from '@/xata' +import { + getRecentApprovedDealsByDate, + getRecentDealsByDate, +} from '@/lib/queries' const client = getXataClient() // Force dynamic routing export const dynamic = 'force-dynamic' export async function GET(req: NextRequest) { - // make sure the request is coming from Vercel - const authToken = (req.headers.get('authorization') || '') - .split('Bearer ') - .at(1) + // make sure the request is coming from Vercel + const authToken = (req.headers.get('authorization') || '') + .split('Bearer ') + .at(1) - if (!authToken || authToken != process.env.CRON_SECRET) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } + if (!authToken || authToken != process.env.CRON_SECRET) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } - // get all approved deals within the last month - const thirtyDaysAgo = subDays(new Date(), 30) - const deals = await client.db.deals.filter({ - approved: true, - 'xata.createdAt': { $ge: thirtyDaysAgo }, - }).getMany() + // get all approved deals within the last month + const thirtyDaysAgo = subDays(new Date(), 30) + const deals = await getRecentApprovedDealsByDate(thirtyDaysAgo) - // categorize the deals - const courses = deals.filter((deal) => deal.category === 'Course') - const conferences = deals.filter((deal) => deal.category === 'Conference') - const ebooks = deals.filter((deal) => deal.category === 'Ebook') - const tools = deals.filter((deal) => deal.category === 'Tool') - const misc = deals.filter((deal) => deal.category === 'Misc') - const officeEquipment = deals.filter( - (deal) => deal.category === 'Office Equipment' - ) + // categorize the deals + const courses = deals.filter((deal) => deal.category === 'Course') + const conferences = deals.filter((deal) => deal.category === 'Conference') + const ebooks = deals.filter((deal) => deal.category === 'Ebook') + const tools = deals.filter((deal) => deal.category === 'Tool') + const misc = deals.filter((deal) => deal.category === 'Misc') + const officeEquipment = deals.filter( + (deal) => deal.category === 'Office Equipment' + ) - return NextResponse.json({ - courses: courses, - conferences: conferences, - ebooks: ebooks, - tools: tools, - misc: misc, - officeEquipment: officeEquipment, - }) + return NextResponse.json({ + courses: courses, + conferences: conferences, + ebooks: ebooks, + tools: tools, + misc: misc, + officeEquipment: officeEquipment, + }) } diff --git a/src/app/api/deals/approve/route.ts b/src/app/api/deals/approve/route.ts index 20d7749..0b9c5f4 100644 --- a/src/app/api/deals/approve/route.ts +++ b/src/app/api/deals/approve/route.ts @@ -1,37 +1,38 @@ -import { isAdminUser } from '@/utils/auth'; -import { getXataClient } from '@/xata'; -import { auth } from '@clerk/nextjs'; -import { z } from 'zod'; +import { approveDeal } from '@/lib/queries' +import { isAdminUser } from '@/utils/auth' +import { getXataClient } from '@/xata' +import { auth } from '@clerk/nextjs' +import { z } from 'zod' const schema = z.object({ id: z.string(), -}); +}) export async function POST(request: Request) { - const { userId } = auth(); + const { userId } = auth() if (!userId) { - return new Response('Unauthorized', { status: 401 }); + return new Response('Unauthorized', { status: 401 }) } - const isAdmin = await isAdminUser(userId); + const isAdmin = await isAdminUser(userId) if (!isAdmin) { - return new Response('Unauthorized', { status: 401 }); + return new Response('Unauthorized', { status: 401 }) } - const body = await request.json(); - let id; + const body = await request.json() + let id try { - const parsed = schema.parse(body); - id = parsed.id; + const parsed = schema.parse(body) + id = parsed.id } catch (error) { - console.error(error); - return new Response('Bad Request', { status: 400 }); + console.error(error) + return new Response('Bad Request', { status: 400 }) } try { - const xataClient = getXataClient(); - await xataClient.db.deals.update(id, { approved: true }); - return new Response('OK', { status: 200 }); + await approveDeal(id) + + return new Response('OK', { status: 200 }) } catch (error) { - console.error(error); - return new Response('Internal Server Error', { status: 500 }); + console.error(error) + return new Response('Internal Server Error', { status: 500 }) } } diff --git a/src/app/api/deals/reject/route.ts b/src/app/api/deals/reject/route.ts index 6524162..9f95fdf 100644 --- a/src/app/api/deals/reject/route.ts +++ b/src/app/api/deals/reject/route.ts @@ -1,37 +1,37 @@ -import { isAdminUser } from '@/utils/auth'; -import { getXataClient } from '@/xata'; -import { auth } from '@clerk/nextjs'; -import { z } from 'zod'; +import { deleteDeal } from '@/lib/queries' +import { isAdminUser } from '@/utils/auth' +import { getXataClient } from '@/xata' +import { auth } from '@clerk/nextjs' +import { z } from 'zod' const schema = z.object({ id: z.string(), -}); +}) export async function POST(request: Request) { - const { userId } = auth(); + const { userId } = auth() if (!userId) { - return new Response('Unauthorized', { status: 401 }); + return new Response('Unauthorized', { status: 401 }) } - const isAdmin = await isAdminUser(userId); + const isAdmin = await isAdminUser(userId) if (!isAdmin) { - return new Response('Unauthorized', { status: 401 }); + return new Response('Unauthorized', { status: 401 }) } - const body = await request.json(); - let id; + const body = await request.json() + let id try { - const parsed = schema.parse(body); - id = parsed.id; + const parsed = schema.parse(body) + id = parsed.id } catch (error) { - console.error(error); - return new Response('Bad Request', { status: 400 }); + console.error(error) + return new Response('Bad Request', { status: 400 }) } try { - const xataClient = getXataClient(); - await xataClient.db.deals.delete(id); - return new Response('OK', { status: 200 }); + await deleteDeal(id) + return new Response('OK', { status: 200 }) } catch (error) { - console.error(error); - return new Response('Internal Server Error', { status: 500 }); + console.error(error) + return new Response('Internal Server Error', { status: 500 }) } } diff --git a/src/app/api/deals/route.tsx b/src/app/api/deals/route.tsx index 22da76a..7b2ed07 100644 --- a/src/app/api/deals/route.tsx +++ b/src/app/api/deals/route.tsx @@ -1,56 +1,55 @@ -import { FORM_DEAL_SCHEMA } from '@/types/Types'; -import { getXataClient } from '@/xata'; -import { NextRequest } from 'next/server'; +import { createDeal } from '@/lib/queries' +import { FORM_DEAL_SCHEMA } from '@/types/Types' +import { getXataClient } from '@/xata' +import { NextRequest } from 'next/server' export async function GET(request: NextRequest) { - const searchParams = request.nextUrl.searchParams; - const query = searchParams.get('query'); + const searchParams = request.nextUrl.searchParams + const query = searchParams.get('query') if (!query) { - return new Response('Bad Request', { status: 400 }); + return new Response('Bad Request', { status: 400 }) } - const xataClient = getXataClient(); + const xataClient = getXataClient() try { + //TODO: how do we handle this now that we have Prisma? const { records } = await xataClient.db.deals.search(query, { target: ['name', 'description'], - }); + }) return new Response(JSON.stringify(records), { headers: { 'content-type': 'application/json' }, - }); + }) } catch (error) { - console.error(error); - return new Response('Bad Request', { status: 400 }); + console.error(error) + return new Response('Bad Request', { status: 400 }) } } export async function POST(request: Request) { - const body = await request.json(); - let parsed; + const body = await request.json() + let parsed try { - parsed = FORM_DEAL_SCHEMA.parse(body); - console.log(parsed); + parsed = FORM_DEAL_SCHEMA.parse(body) + console.log(parsed) } catch (error) { - console.log('failed to parse'); + console.log('failed to parse') // console.error(error); - return new Response('Bad Request', { status: 400 }); + return new Response('Bad Request', { status: 400 }) } try { const newDeal = { ...parsed, - image: { name: parsed.name, mediaType: 'image/png', base64Content: '' }, - }; + // image: { name: parsed.name, mediaType: 'image/png', base64Content: '' }, + } - const xataClient = getXataClient(); - const createdRecord = await xataClient.db.deals.create(newDeal, [ - 'image.uploadUrl', - ]); + const createdRecord = await createDeal(newDeal) return new Response(JSON.stringify(createdRecord), { headers: { 'content-type': 'application/json' }, - }); + }) } catch (error) { - console.error(error); - return new Response(JSON.stringify(error), { status: 500 }); + console.error(error) + return new Response(JSON.stringify(error), { status: 500 }) } } diff --git a/src/app/deals/[category]/page.tsx b/src/app/deals/[category]/page.tsx index 17970a6..1be7569 100644 --- a/src/app/deals/[category]/page.tsx +++ b/src/app/deals/[category]/page.tsx @@ -2,18 +2,22 @@ import { DealsRecord, getXataClient } from '@/xata' import CategoryOptions from '@/components/CategoryOptions' import DealsList from '../../../components/deals/DealsList' import { redirect } from 'next/navigation' +import { getApprovedDealsByCategory } from '@/lib/queries' +import { Category } from '@/types/Types' export default async function CategoryPage({ params, }: { params: { category: string } }) { - const { category } = params - // const xataClient = getXataClient() - // const deals: DealsRecord[] = await xataClient.db.deals - // .filter({ approved: true, category }) - // .sort('xata.createdAt', 'desc') - // .getAll() + const { category: categoryString } = params + //check for valid category + if (!(categoryString in Category)) { + redirect('/') + } + // const category = categoryString as Category; + + // const deals = await getApprovedDealsByCategory(category); redirect('/') return (
      diff --git a/src/app/deals/page.tsx b/src/app/deals/page.tsx index 54aee87..3c7406b 100644 --- a/src/app/deals/page.tsx +++ b/src/app/deals/page.tsx @@ -1,16 +1,18 @@ import CategoryOptions from '@/components/CategoryOptions' import DealsList from '../../components/deals/DealsList' import { redirect } from 'next/navigation' +import { getApprovedDeals, getRecentApprovedDealsByDate } from '@/lib/queries' export default async function DealsPage() { - redirect('/') + const deals = await getApprovedDeals(20) + //TODO: handle error return (

      Top Deals

      - {/* */} +
      ) } diff --git a/src/components/deals/Deal.tsx b/src/components/deals/Deal.tsx index 4627229..525836b 100644 --- a/src/components/deals/Deal.tsx +++ b/src/components/deals/Deal.tsx @@ -8,6 +8,7 @@ import { FaBeer, FaVideo, FaBook, FaCog, FaCalendar } from 'react-icons/fa' import { Category } from '@/types/Types' import { format } from 'date-fns' import { start } from 'repl' +import { Deal } from '@prisma/client' const categoryToIcon: { [key: string]: JSX.Element } = { Misc: , @@ -17,11 +18,11 @@ const categoryToIcon: { [key: string]: JSX.Element } = { Conference: , } -export default function Deal({ +export default function DealCard({ deal, showAdminOptions = false, }: { - deal: DealsRecord + deal: Deal showAdminOptions?: boolean }) { if (!deal || !deal.startDate) { diff --git a/src/components/deals/DealsList.tsx b/src/components/deals/DealsList.tsx index 256912e..8902e07 100644 --- a/src/components/deals/DealsList.tsx +++ b/src/components/deals/DealsList.tsx @@ -1,12 +1,13 @@ 'use client' -import Deal from '@/components/deals/Deal' +import DealCard from '@/components/deals/Deal' import { DealsRecord } from '@/xata' +import { Deal } from '@prisma/client' export default function DealsList({ deals, isAdmin = false, }: { - deals: DealsRecord[] + deals: Deal[] isAdmin?: boolean }) { return ( @@ -14,7 +15,7 @@ export default function DealsList({ {deals.length > 0 && (
      {deals.map((deal) => ( - + ))}
      )} diff --git a/src/components/deals/FeaturedDeals.tsx b/src/components/deals/FeaturedDeals.tsx index eee1c20..572c74b 100644 --- a/src/components/deals/FeaturedDeals.tsx +++ b/src/components/deals/FeaturedDeals.tsx @@ -1,4 +1,5 @@ import DealsList from '@/components/deals/DealsList' +import { getApprovedFeaturedDeals } from '@/lib/queries' import { Category } from '@/types/Types' import { getXataClient } from '@/xata' import React from 'react' @@ -8,15 +9,7 @@ export default async function FeaturedDeals({ }: { category: Category }) { - const xataClient = getXataClient() - const deals = await xataClient.db.deals - .filter({ approved: true, featured: true, category }) - .sort('xata.createdAt', 'desc') - .getMany({ - pagination: { - size: 3, - }, - }) + const deals = await getApprovedFeaturedDeals(3) if (!deals || deals.length === 0) return null return ( diff --git a/src/lib/db.ts b/src/lib/db.ts new file mode 100644 index 0000000..f4ba39b --- /dev/null +++ b/src/lib/db.ts @@ -0,0 +1,15 @@ +import { PrismaClient } from '@prisma/client' + +const prismaClientSingleton = () => { + return new PrismaClient() +} + +declare global { + var prismaGlobal: undefined | ReturnType +} + +const prisma = globalThis.prismaGlobal ?? prismaClientSingleton() + +export default prisma + +if (process.env.NODE_ENV !== 'production') globalThis.prismaGlobal = prisma diff --git a/src/lib/queries.ts b/src/lib/queries.ts index 6183c5c..844b762 100644 --- a/src/lib/queries.ts +++ b/src/lib/queries.ts @@ -1,67 +1,138 @@ 'use server' -import { NewSubscriberData } from '@/types/Types' -import { Status } from '@/types/Types' - -import { getXataClient, DealsRecord, Subscribers } from '@/xata' -const xataClient = getXataClient() +import { Category, NewSubscriberData, Status } from '@/types/Types' +import prisma from './db' +import { Deal, Subscriber } from '@prisma/client' // deal queries export async function getAllDeals() { - const deals: DealsRecord[] = await xataClient.db.deals - .sort('xata.createdAt', 'desc') - .getMany() - + const deals = await prisma.deal.findMany({ + orderBy: { + xata_createdat: 'desc', + }, + }) return deals } // subscriber queries export async function createSubscriber( newSubscriberData: NewSubscriberData -): Promise { - const newSubscriber = - await xataClient.db.subscribers.create(newSubscriberData) - - return newSubscriber +): Promise { + return await prisma.subscriber.create({ + data: newSubscriberData, + }) } -export async function getOneSubscriberByToken(token: string) { - const subscriber = await xataClient.db.subscribers - .filter({ +export async function getOneSubscriberByToken( + token: string +): Promise { + return await prisma.subscriber.findFirst({ + where: { token, - }) - .getFirst() - - return subscriber + }, + }) } -export async function getOneSubscriberByEmail(email: string) { - const subscriber = await xataClient.db.subscribers - .filter({ +export async function getOneSubscriberByEmail( + email: string +): Promise { + return await prisma.subscriber.findFirst({ + where: { email, - }) - .getFirst() + }, + }) +} +export async function getAllSubscribers(): Promise { + return await prisma.subscriber.findMany() +} + +export async function updateSubscriberToVerified( + id: string +): Promise { + return await prisma.subscriber.update({ + where: { + xata_id: id, + }, + data: { + verified: true, + status: Status.SUBSCRIBED, + }, + }) +} + +export async function approveDeal(id: string): Promise { + return await prisma.deal.update({ + where: { + xata_id: id, + }, + data: { + approved: true, + }, + }) +} - return subscriber +export async function getRecentApprovedDealsByDate( + date: Date +): Promise { + return await prisma.deal.findMany({ + where: { + approved: true, + xata_createdat: { + gte: date, + }, + }, + }) } -export async function getAllSubscribers(): Promise { - const subscribers = await xataClient.db.subscribers.getMany({}) - return subscribers +export async function getApprovedDeals(limit: number = 20): Promise { + return await prisma.deal.findMany({ + where: { + approved: true, + }, + take: limit, + }) +} +export async function getApprovedDealsByCategory( + category: Category +): Promise { + return await prisma.deal.findMany({ + where: { + approved: true, + category, + }, + }) } -export async function updateSubscriberToVerified(id: string) { - const data = await xataClient.db.subscribers.update(id, { - verified: true, - status: Status.SUBSCRIBED, +//TODO: get a type from prisma for new deal +export const createDeal = async (newDeal: any) => { + return await prisma.deal.create({ + data: newDeal, }) +} - return data +export const getAdminUserById = async (userId: string) => { + return await prisma.adminUser.findUnique({ + where: { + userId, + }, + }) +} + +export async function getApprovedFeaturedDeals( + limit: number = 20 +): Promise { + return await prisma.deal.findMany({ + where: { + approved: true, + featured: true, + }, + take: limit, + }) } export async function updateSubscriberPreferences( id: string, - subscriberData: Subscribers -) { + subscriberData: Subscriber +): Promise { const { courseNotifications, ebookNotifications, @@ -84,9 +155,26 @@ export async function updateSubscriberPreferences( status: isSubscribed ? Status.SUBSCRIBED : Status.UNSUBSCRIBED, } - await xataClient.db.subscribers.update(id, subscriber) + return await prisma.subscriber.update({ + where: { + xata_id: id, + }, + data: subscriber, + }) } -export async function deleteSubscriber(id: string) { - const data = await xataClient.db.subscribers.delete(id) +export async function deleteSubscriber(id: string): Promise { + return await prisma.subscriber.delete({ + where: { + xata_id: id, + }, + }) +} + +export async function deleteDeal(id: string): Promise { + return await prisma.deal.delete({ + where: { + xata_id: id, + }, + }) } diff --git a/src/types/Types.ts b/src/types/Types.ts index dcd3908..311af3b 100644 --- a/src/types/Types.ts +++ b/src/types/Types.ts @@ -11,8 +11,8 @@ export enum Category { } export enum Status { - SUBSCRIBED = 'subscribed', - UNSUBSCRIBED = 'unsubscribed', + SUBSCRIBED = 'SUBSCRIBED', + UNSUBSCRIBED = 'UNSUBSCRIBED', } export const FORM_DEAL_SCHEMA = z.object({ @@ -23,7 +23,7 @@ export const FORM_DEAL_SCHEMA = z.object({ endDate: z.coerce.date(), coupon: z.string().optional().optional(), couponPercent: z.number().optional(), - email: z.string().email().optional(), + email: z.string().email(), category: z.nativeEnum(Category), }) @@ -36,4 +36,5 @@ export type NewSubscriberData = { officeEquipmentNotifications: boolean toolNotifications: boolean conferenceNotifications: boolean + status: string } diff --git a/src/types/env.ts b/src/types/env.ts index 316baa7..4c55d6a 100644 --- a/src/types/env.ts +++ b/src/types/env.ts @@ -19,6 +19,7 @@ const envVariables = z.object({ NEXT_PUBLIC_POSTHOG_KEY: z.string().optional(), NEXT_PUBLIC_POSTHOG_HOST: z.string().optional(), NEXT_PUBLIC_ENVIRONMENT: z.string(), + DATABASE_URL: z.string(), }) const { @@ -38,6 +39,7 @@ const { REPLY_TO_EMAIL, NEXT_PUBLIC_BASE_URL, NEXT_PUBLIC_ENVIRONMENT, + DATABASE_URL, } = process.env const envServerSchema = envVariables.safeParse({ @@ -57,6 +59,7 @@ const envServerSchema = envVariables.safeParse({ REPLY_TO_EMAIL, NEXT_PUBLIC_BASE_URL, NEXT_PUBLIC_ENVIRONMENT, + DATABASE_URL, }) if (!envServerSchema.success) { diff --git a/src/utils/auth.ts b/src/utils/auth.ts index b806931..54206ed 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -1,8 +1,7 @@ -import { getXataClient } from '@/xata'; +import { getAdminUserById } from '@/lib/queries' export const isAdminUser = async (userId: string): Promise => { - if (!userId) return false; - const xataClient = getXataClient(); - const adminUser = await xataClient.db.adminUser.read(userId); - return !!adminUser; -}; + if (!userId) return false + const adminUser = await getAdminUserById(userId) + return !!adminUser +} From 0f2f1ed9e1539c2c466cba5f2ced58d02b376676 Mon Sep 17 00:00:00 2001 From: jamesqquick Date: Fri, 22 Mar 2024 14:42:42 -0500 Subject: [PATCH 031/127] feat: migrate db query code to Prisma --- .gitignore | 2 + package-lock.json | 127 ++++++++++++++++++++++++++ package.json | 1 + prisma/schema.prisma | 16 ++-- src/actions/deals.ts | 30 +++--- src/actions/subscriber-subscribe.ts | 2 +- src/app/(admin)/dashboard/page.tsx | 10 +- src/app/deals/add/DealForm.tsx | 137 ++++++++++++++-------------- src/app/page.tsx | 7 +- src/components/AdminOptions.tsx | 40 ++++---- src/components/deals/DealsList.tsx | 6 +- src/lib/queries.ts | 14 ++- src/types/Types.ts | 2 + 13 files changed, 270 insertions(+), 124 deletions(-) diff --git a/.gitignore b/.gitignore index 7444bf5..3d7fe08 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ /.next/ /out/ +.frontmatter + # production /build diff --git a/package-lock.json b/package-lock.json index 5aca672..60ca0e4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,7 @@ "tailwind-merge": "^2.2.1", "tailwindcss-animate": "^1.0.7", "ts-key-enum": "^2.0.12", + "ts-node": "^10.9.2", "uuid": "^9.0.1", "zod": "^3.22.4" }, @@ -769,6 +770,26 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@emotion/is-prop-valid": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", @@ -2882,6 +2903,26 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" + }, "node_modules/@types/body-parser": { "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", @@ -3490,6 +3531,14 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", @@ -4608,6 +4657,11 @@ "typescript": ">=4" } }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -4930,6 +4984,14 @@ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -7921,6 +7983,11 @@ "node": ">=12" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" + }, "node_modules/map-obj": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", @@ -11166,6 +11233,53 @@ "resolved": "https://registry.npmjs.org/ts-key-enum/-/ts-key-enum-2.0.12.tgz", "integrity": "sha512-Ety4IvKMaeG34AyXMp5r11XiVZNDRL+XWxXbVVJjLvq2vxKRttEANBE7Za1bxCAZRdH2/sZT6jFyyTWxXz28hw==" }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -11434,6 +11548,11 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -11879,6 +11998,14 @@ "node": ">=12" } }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 71102ba..047e18d 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "tailwind-merge": "^2.2.1", "tailwindcss-animate": "^1.0.7", "ts-key-enum": "^2.0.12", + "ts-node": "^10.9.2", "uuid": "^9.0.1", "zod": "^3.22.4" }, diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b01e973..6e5d636 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -8,17 +8,18 @@ datasource db { } model Deal { - coupon String? @db.VarChar(50) + id String @id @default(uuid()) + coupon String? @db.VarChar(255) approved Boolean @default(false) description String @db.VarChar(255) couponPercent Int? @default(0) featured Boolean @default(false) - name String @db.VarChar(50) + name String @db.VarChar(255) link String @db.VarChar(255) - category String @db.VarChar(20) + category String @db.VarChar(50) startDate DateTime @default(now()) endDate DateTime @default(now()) - email String @db.VarChar(50) + email String @db.VarChar(255) xata_id String @unique(map: "Deal__pgroll_new_xata_id_key") @default(dbgenerated("('rec_'::text || (xata_private.xid())::text)")) xata_version Int @default(0) xata_createdat DateTime @default(now()) @db.Timestamptz(6) @@ -34,10 +35,11 @@ model AdminUser { } model Subscriber { - status String @db.VarChar(20) - email String @db.VarChar(50) + id String @id @default(uuid()) + status String @db.Text + email String @db.VarChar(255) verified Boolean @default(false) - token String @db.VarChar(50) + token String @db.VarChar(255) conferenceNotifications Boolean @default(false) ebookNotifications Boolean @default(false) courseNotifications Boolean @default(false) diff --git a/src/actions/deals.ts b/src/actions/deals.ts index de41cb2..27a6ad0 100644 --- a/src/actions/deals.ts +++ b/src/actions/deals.ts @@ -4,6 +4,8 @@ import { DealsRecord, getXataClient } from '@/xata' import { redirect } from 'next/navigation' import { z } from 'zod' import prisma from '@/lib/db' +import { Deal } from '@prisma/client' +import { ReturnValue } from '@/types/Types' const dealSchema = z.object({ name: z.string(), @@ -18,40 +20,32 @@ const dealSchema = z.object({ category: z.enum(['misc', 'ebook', 'video', 'tool', 'conference']), }) -export async function createDeal(formData: FormData) { +export async function createDeal( + formData: FormData +): Promise> { let parsed try { parsed = dealSchema.safeParse({ name: formData.get('name'), - coupon: formData.get('coupon'), + coupon: formData.get('coupon') || undefined, link: formData.get('link'), startDate: formData.get('startDate'), endDate: formData.get('endDate'), description: formData.get('description'), - couponPercent: formData.get('couponPercent'), - email: formData.get('email') || undefined, + couponPercent: formData.get('couponPercent') || undefined, + email: formData.get('email'), category: formData.get('category'), }) } catch (error) { - return console.error(error) - } - if (!parsed.success) return { error: parsed.error } - const newDeal = { - name: parsed.data.name, - coupon: parsed.data.coupon, - link: parsed.data.link, - startDate: parsed.data.startDate, - endDate: parsed.data.endDate, - description: parsed.data.description, - couponPercent: parsed.data.couponPercent, - email: parsed.data.email, - category: parsed.data.category, + console.error(error) + return { error: 'Invalid form data' } } + if (!parsed.success) return { error: parsed.error.message } + const newDeal = parsed.data const createdRecord = await prisma.deal.create({ data: newDeal, }) - console.log(createdRecord) redirect(`/thank-you`) } diff --git a/src/actions/subscriber-subscribe.ts b/src/actions/subscriber-subscribe.ts index b360a7f..acb92b4 100644 --- a/src/actions/subscriber-subscribe.ts +++ b/src/actions/subscriber-subscribe.ts @@ -21,7 +21,7 @@ export const subscribe = async (formData: FormData) => { const existingSubscriber = await getOneSubscriberByEmail(checkedEmail) if (existingSubscriber) { - console.log('Subscriber exists') + console.info(`Subscriber exists: ${checkedEmail}`) return { error: 'This email already exists', } diff --git a/src/app/(admin)/dashboard/page.tsx b/src/app/(admin)/dashboard/page.tsx index 9bd0623..c0b6a77 100644 --- a/src/app/(admin)/dashboard/page.tsx +++ b/src/app/(admin)/dashboard/page.tsx @@ -2,7 +2,11 @@ import { auth } from '@clerk/nextjs' import { redirect } from 'next/navigation' import { isAdminUser } from '@/utils/auth' import Dashboard from '@/components/dashboard/Dashboard' -import { getAllDeals, getAllSubscribers } from '@/lib/queries' +import { + getAllDeals, + getAllSubscribers, + getAllUnapprovedDeals, +} from '@/lib/queries' export default async function Home() { const { userId } = auth() @@ -18,7 +22,7 @@ export default async function Home() { } // fetch all deals and subscribers - const dealsPromise = getAllDeals() + const dealsPromise = getAllUnapprovedDeals() const subscribersPromise = getAllSubscribers() const [deals, subscribers] = await Promise.all([ @@ -26,8 +30,6 @@ export default async function Home() { subscribersPromise, ]) - console.log(subscribers) - return (
      diff --git a/src/app/deals/add/DealForm.tsx b/src/app/deals/add/DealForm.tsx index 58f145c..e8ee129 100644 --- a/src/app/deals/add/DealForm.tsx +++ b/src/app/deals/add/DealForm.tsx @@ -1,42 +1,41 @@ -'use client'; -import CategorySelect from '@/components/forms/CategorySelect'; -import { DatePickerWithRange } from '@/components/forms/DatePicker'; -import DragAndDropImage from '@/components/forms/DragAndDropImage'; -import { Category, FORM_DEAL_SCHEMA } from '@/types/Types'; -import { addDays } from 'date-fns'; -import { redirect } from 'next/navigation'; -import { useState } from 'react'; -import { DateRange } from 'react-day-picker'; -import { useRouter } from 'next/navigation'; +'use client' +import CategorySelect from '@/components/forms/CategorySelect' +import { DatePickerWithRange } from '@/components/forms/DatePicker' +import DragAndDropImage from '@/components/forms/DragAndDropImage' +import { Category, FORM_DEAL_SCHEMA } from '@/types/Types' +import { addDays } from 'date-fns' +import { useState } from 'react' +import { DateRange } from 'react-day-picker' +import { useRouter } from 'next/navigation' export default function DealForm() { - const router = useRouter(); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(); - const [file, setFile] = useState(); - const [nameCharacterCount, setNameCharacterCount] = useState(0); + const router = useRouter() + const [loading, setLoading] = useState(false) + const [error, setError] = useState() + const [file, setFile] = useState() + const [nameCharacterCount, setNameCharacterCount] = useState(0) const [descriptionCharacterCount, setDescriptionCharacterCount] = - useState(0); + useState(0) const [dateRange, setDateRange] = useState({ from: new Date(), to: addDays(new Date(), 7), - }); + }) const [category, setCategory] = useState( Category.COURSE - ); + ) const onNameChange = (e: React.ChangeEvent) => { - setNameCharacterCount(e.target.value?.length || 0); - }; + setNameCharacterCount(e.target.value?.length || 0) + } const onDescriptionChange = (e: React.ChangeEvent) => { - setDescriptionCharacterCount(e.target.value?.length || 0); - }; + setDescriptionCharacterCount(e.target.value?.length || 0) + } const handleFormSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setLoading(true); - const formData = new FormData(e.currentTarget); - let newDeal; + e.preventDefault() + setLoading(true) + const formData = new FormData(e.currentTarget) + let newDeal try { newDeal = { name: formData.get('name'), @@ -48,12 +47,12 @@ export default function DealForm() { couponPercent: Number(formData.get('couponPercent')), email: formData.get('email'), category, - }; - newDeal = FORM_DEAL_SCHEMA.parse(newDeal); + } + newDeal = FORM_DEAL_SCHEMA.parse(newDeal) } catch (error) { - console.log('failed to parse'); - console.error(error); - return new Response('Bad Request', { status: 400 }); + console.log('failed to parse') + console.error(error) + return new Response('Bad Request', { status: 400 }) } try { @@ -63,44 +62,44 @@ export default function DealForm() { headers: { 'Content-Type': 'application/json', }, - }); + }) if (res.ok) { - const createdDeal = await res.json(); - const { image } = createdDeal; - const { uploadUrl } = image; + const createdDeal = await res.json() + const { image } = createdDeal + const { uploadUrl } = image try { const res = await fetch(uploadUrl, { method: 'PUT', body: file, - }); - console.log(res); + }) + console.log(res) if (res.ok) { - console.log('uploaded'); - router.push('/thank-you'); + console.log('uploaded') + router.push('/thank-you') } else { - setError('Image upload failed'); + setError('Image upload failed') } } catch (error) { - setError(error); - console.error(error); + setError(error) + console.error(error) } } } catch (error) { - console.error(error); - setError(error); + console.error(error) + setError(error) } finally { - setLoading(false); + setLoading(false) } - }; + } return (
      -

      Media

      +

      Media

      -

      +

      Product Details

      @@ -118,7 +117,7 @@ export default function DealForm() {