diff --git a/client/src/app/auth/login/_components/LoginForm.tsx b/client/src/app/auth/login/_components/LoginForm.tsx index 2564983..b5ee87c 100644 --- a/client/src/app/auth/login/_components/LoginForm.tsx +++ b/client/src/app/auth/login/_components/LoginForm.tsx @@ -63,7 +63,13 @@ export default function LoginForm() { await magicLinkFn.mutateAsync({ email, redirect: redirectTo }); notifications.show({ title: 'Login link sent', - message: 'Check your email!', + message: ( + <> + Check your email! +
+ (You might need to check your spam filter.) + + ), }); setEmail(''); } catch (e) { diff --git a/client/src/app/auth/logout/_components/LogoutAction.tsx b/client/src/app/auth/logout/_components/LogoutAction.tsx index 0709fdd..9153049 100644 --- a/client/src/app/auth/logout/_components/LogoutAction.tsx +++ b/client/src/app/auth/logout/_components/LogoutAction.tsx @@ -15,9 +15,12 @@ export default function LogoutAction() { // notice when the user object disappears const user = useUser(); - const router = useRouter(); useEffect(() => { - if (!user) router.push('/'); + if (!user) { + if (window) { + window.location.href = '/'; + } + } // eslint-disable-next-line react-hooks/exhaustive-deps }, [user]); diff --git a/server/.gitignore b/server/.gitignore index be8ba6a..b871b85 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -2,3 +2,4 @@ node_modules/ .serverless .env .esbuild/ +.dist/ diff --git a/server/package.json b/server/package.json index 61d44af..be91510 100644 --- a/server/package.json +++ b/server/package.json @@ -2,10 +2,11 @@ "name": "epc-next__server", "private": true, "scripts": { - "dev": "nodemon --exec \"sls offline start\"", + "dev": "nodemon --exec \"sls offline start\" -e js,mjs,cjs,json,tsx", "push": "sls deploy", "gql:gen": "graphql-codegen -c ./src/db/codegen.ts", - "postinstall": "pnpm run gql:gen" + "postinstall": "pnpm run gql:gen", + "email:dev": "email dev --dir \"src/email/templates\" --port 3008" }, "dependencies": { "@aws-sdk/client-dynamodb": "^3.590.0", @@ -17,6 +18,8 @@ "@getbrevo/brevo": "^2.1.1", "@graphql-typed-document-node/core": "^3.2.0", "@passwordlessdev/passwordless-nodejs": "^0.2.0", + "@react-email/components": "^0.0.25", + "@react-email/render": "^1.0.1", "@trpc/server": "11.0.0-rc.396", "axios": "^1.7.2", "cors": "^2.8.5", @@ -34,11 +37,13 @@ "jose": "^5.4.0", "mime": "^4.0.3", "qs": "^6.12.1", + "react-dom": "^18.3.1", "rehype-stringify": "^10.0.0", "remark-breaks": "^4.0.0", "remark-gfm": "^4.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.0", + "resend": "^4.0.0", "serverless-http": "^3.2.0", "textversionjs": "^1.1.3", "unified": "^11.0.4", @@ -55,10 +60,12 @@ "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/qs": "^6.9.15", + "@types/react-dom": "^18.3.0", "@types/textversionjs": "^1.1.4", "dotenv": "^16.4.5", "esbuild": "^0.20.0", "nodemon": "^3.1.3", + "react-email": "^3.0.1", "serverless-esbuild": "^1.52.1", "serverless-ignore": "^0.2.1", "serverless-offline": "^13.6.0", diff --git a/server/pnpm-lock.yaml b/server/pnpm-lock.yaml index 749f078..16a3c62 100644 --- a/server/pnpm-lock.yaml +++ b/server/pnpm-lock.yaml @@ -35,6 +35,12 @@ importers: '@passwordlessdev/passwordless-nodejs': specifier: ^0.2.0 version: 0.2.0 + '@react-email/components': + specifier: ^0.0.25 + version: 0.0.25(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-email/render': + specifier: ^1.0.1 + version: 1.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@trpc/server': specifier: 11.0.0-rc.396 version: 11.0.0-rc.396 @@ -86,6 +92,9 @@ importers: qs: specifier: ^6.12.1 version: 6.12.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) rehype-stringify: specifier: ^10.0.0 version: 10.0.0 @@ -101,6 +110,9 @@ importers: remark-rehype: specifier: ^11.1.0 version: 11.1.0 + resend: + specifier: ^4.0.0 + version: 4.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) serverless-http: specifier: ^3.2.0 version: 3.2.0 @@ -144,6 +156,9 @@ importers: '@types/qs': specifier: ^6.9.15 version: 6.9.15 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.0 '@types/textversionjs': specifier: ^1.1.4 version: 1.1.4 @@ -156,6 +171,9 @@ importers: nodemon: specifier: ^3.1.3 version: 3.1.3 + react-email: + specifier: ^3.0.1 + version: 3.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) serverless-esbuild: specifier: ^1.52.1 version: 1.52.1(esbuild@0.20.2) @@ -696,6 +714,10 @@ packages: resolution: {integrity: sha512-aC2DGhBq5eEdyXWqrDInSqQjO0k8xtPRf5YylULqx8MCd6jBtzqfta/3ETMRpuKIc5hyswfO80ObyA1MvkCcUQ==} engines: {node: '>=6.9.0'} + '@babel/core@7.24.5': + resolution: {integrity: sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==} + engines: {node: '>=6.9.0'} + '@babel/core@7.24.6': resolution: {integrity: sha512-qAHSfAdVyFmIvl0VHELib8xar7ONuSHrE2hLnsaWkYNTI68dmi1x8GYDhJjMI/e7XWal9QBlZkwbOnkcw7Z8gQ==} engines: {node: '>=6.9.0'} @@ -790,6 +812,11 @@ packages: resolution: {integrity: sha512-2YnuOp4HAk2BsBrJJvYCbItHx0zWscI1C3zgWkz+wDyD9I7GIVrfnLyrR4Y1VR+7p+chAEcrgRQYZAGIKMV7vQ==} engines: {node: '>=6.9.0'} + '@babel/parser@7.24.5': + resolution: {integrity: sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/parser@7.24.6': resolution: {integrity: sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==} engines: {node: '>=6.0.0'} @@ -989,138 +1016,276 @@ packages: resolution: {integrity: sha512-IPjmgSc4KpQRlO4qbEDnBEixvtb06WDmjKfi/7fkZaryh5HuOmTtixe1EupQI5XfXO8joc3d27uUZ0QdC++euA==} engines: {node: '>=18.0.0'} + '@esbuild/aix-ppc64@0.19.11': + resolution: {integrity: sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.20.2': resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.19.11': + resolution: {integrity: sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.20.2': resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} engines: {node: '>=12'} cpu: [arm64] os: [android] + '@esbuild/android-arm@0.19.11': + resolution: {integrity: sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.20.2': resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} engines: {node: '>=12'} cpu: [arm] os: [android] + '@esbuild/android-x64@0.19.11': + resolution: {integrity: sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.20.2': resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} engines: {node: '>=12'} cpu: [x64] os: [android] + '@esbuild/darwin-arm64@0.19.11': + resolution: {integrity: sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.20.2': resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-x64@0.19.11': + resolution: {integrity: sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.20.2': resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} engines: {node: '>=12'} cpu: [x64] os: [darwin] + '@esbuild/freebsd-arm64@0.19.11': + resolution: {integrity: sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.20.2': resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-x64@0.19.11': + resolution: {integrity: sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.20.2': resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] + '@esbuild/linux-arm64@0.19.11': + resolution: {integrity: sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.20.2': resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} engines: {node: '>=12'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm@0.19.11': + resolution: {integrity: sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.20.2': resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} engines: {node: '>=12'} cpu: [arm] os: [linux] + '@esbuild/linux-ia32@0.19.11': + resolution: {integrity: sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.20.2': resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} engines: {node: '>=12'} cpu: [ia32] os: [linux] + '@esbuild/linux-loong64@0.19.11': + resolution: {integrity: sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.20.2': resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} engines: {node: '>=12'} cpu: [loong64] os: [linux] + '@esbuild/linux-mips64el@0.19.11': + resolution: {integrity: sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.20.2': resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] + '@esbuild/linux-ppc64@0.19.11': + resolution: {integrity: sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.20.2': resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] + '@esbuild/linux-riscv64@0.19.11': + resolution: {integrity: sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.20.2': resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] + '@esbuild/linux-s390x@0.19.11': + resolution: {integrity: sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.20.2': resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} engines: {node: '>=12'} cpu: [s390x] os: [linux] + '@esbuild/linux-x64@0.19.11': + resolution: {integrity: sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.20.2': resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} engines: {node: '>=12'} cpu: [x64] os: [linux] + '@esbuild/netbsd-x64@0.19.11': + resolution: {integrity: sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.20.2': resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] + '@esbuild/openbsd-x64@0.19.11': + resolution: {integrity: sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.20.2': resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] + '@esbuild/sunos-x64@0.19.11': + resolution: {integrity: sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.20.2': resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} engines: {node: '>=12'} cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.19.11': + resolution: {integrity: sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.20.2': resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} engines: {node: '>=12'} cpu: [arm64] os: [win32] + '@esbuild/win32-ia32@0.19.11': + resolution: {integrity: sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.20.2': resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} engines: {node: '>=12'} cpu: [ia32] os: [win32] + '@esbuild/win32-x64@0.19.11': + resolution: {integrity: sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.20.2': resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} engines: {node: '>=12'} @@ -1499,6 +1664,10 @@ packages: resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} deprecated: Use @eslint/object-schema instead + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + '@jridgewell/gen-mapping@0.3.5': resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} engines: {node: '>=6.0.0'} @@ -1526,6 +1695,63 @@ packages: '@kwsites/promise-deferred@1.1.1': resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} + '@next/env@14.2.3': + resolution: {integrity: sha512-W7fd7IbkfmeeY2gXrzJYDx8D2lWKbVoTIj1o1ScPHNzvp30s1AuoEFSdr39bC5sjxJaxTtq3OTCZboNp0lNWHA==} + + '@next/swc-darwin-arm64@14.2.3': + resolution: {integrity: sha512-3pEYo/RaGqPP0YzwnlmPN2puaF2WMLM3apt5jLW2fFdXD9+pqcoTzRk+iZsf8ta7+quAe4Q6Ms0nR0SFGFdS1A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@next/swc-darwin-x64@14.2.3': + resolution: {integrity: sha512-6adp7waE6P1TYFSXpY366xwsOnEXM+y1kgRpjSRVI2CBDOcbRjsJ67Z6EgKIqWIue52d2q/Mx8g9MszARj8IEA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@next/swc-linux-arm64-gnu@14.2.3': + resolution: {integrity: sha512-cuzCE/1G0ZSnTAHJPUT1rPgQx1w5tzSX7POXSLaS7w2nIUJUD+e25QoXD/hMfxbsT9rslEXugWypJMILBj/QsA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-arm64-musl@14.2.3': + resolution: {integrity: sha512-0D4/oMM2Y9Ta3nGuCcQN8jjJjmDPYpHX9OJzqk42NZGJocU2MqhBq5tWkJrUQOQY9N+In9xOdymzapM09GeiZw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-x64-gnu@14.2.3': + resolution: {integrity: sha512-ENPiNnBNDInBLyUU5ii8PMQh+4XLr4pG51tOp6aJ9xqFQ2iRI6IH0Ds2yJkAzNV1CfyagcyzPfROMViS2wOZ9w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-linux-x64-musl@14.2.3': + resolution: {integrity: sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-win32-arm64-msvc@14.2.3': + resolution: {integrity: sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@next/swc-win32-ia32-msvc@14.2.3': + resolution: {integrity: sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@next/swc-win32-x64-msvc@14.2.3': + resolution: {integrity: sha512-Q1/zm43RWynxrO7lW4ehciQVj+5ePBhOK+/K2P7pLFX3JaJ/IZVC69SHidrmZSOkqz7ECIOhhy7XhAFG4JYyHA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1538,6 +1764,9 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@one-ini/wasm@0.1.1': + resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + '@parcel/watcher-android-arm64@2.4.1': resolution: {integrity: sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg==} engines: {node: '>= 10.0.0'} @@ -1629,9 +1858,148 @@ packages: resolution: {integrity: sha512-BRs5XUAwiyCDQMsVA9IDvDa7UBR9gAvPHgugOeGng3YN6vJ9JYonyDc0lNczErgtCWtucjR5N7VtaonboD/ezg==} engines: {node: '>=10.12.0'} + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@react-email/body@0.0.10': + resolution: {integrity: sha512-dMJyL9aU25ieatdPtVjCyQ/WHZYHwNc+Hy/XpF8Cc18gu21cUynVEeYQzFSeigDRMeBQ3PGAyjVDPIob7YlGwA==} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/button@0.0.17': + resolution: {integrity: sha512-ioHdsk+BpGS/PqjU6JS7tUrVy9yvbUx92Z+Cem2+MbYp55oEwQ9VHf7u4f5NoM0gdhfKSehBwRdYlHt/frEMcg==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/code-block@0.0.9': + resolution: {integrity: sha512-Zrhc71VYrSC1fVXJuaViKoB/dBjxLw6nbE53Bm/eUuZPdnnZ1+ZUIh8jfaRKC5MzMjgnLGQTweGXVnfIrhyxtQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/code-inline@0.0.4': + resolution: {integrity: sha512-zj3oMQiiUCZbddSNt3k0zNfIBFK0ZNDIzzDyBaJKy6ZASTtWfB+1WFX0cpTX8q0gUiYK+A94rk5Qp68L6YXjXQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/column@0.0.12': + resolution: {integrity: sha512-Rsl7iSdDaeHZO938xb+0wR5ud0Z3MVfdtPbNKJNojZi2hApwLAQXmDrnn/AcPDM5Lpl331ZljJS8vHTWxxkvKw==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/components@0.0.25': + resolution: {integrity: sha512-lnfVVrThEcET5NPoeaXvrz9UxtWpGRcut2a07dLbyKgNbP7vj/cXTI5TuHtanCvhCddFpMDnElNRghDOfPzwUg==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/container@0.0.14': + resolution: {integrity: sha512-NgoaJJd9tTtsrveL86Ocr/AYLkGyN3prdXKd/zm5fQpfDhy/NXezyT3iF6VlwAOEUIu64ErHpAJd+P6ygR+vjg==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/font@0.0.8': + resolution: {integrity: sha512-fSBEqYyVPAyyACBBHcs3wEYzNknpHMuwcSAAKE8fOoDfGqURr/vSxKPdh4tOa9z7G4hlcEfgGrCYEa2iPT22cw==} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/head@0.0.11': + resolution: {integrity: sha512-skw5FUgyamIMK+LN+fZQ5WIKQYf0dPiRAvsUAUR2eYoZp9oRsfkIpFHr0GWPkKAYjFEj+uJjaxQ/0VzQH7svVg==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/heading@0.0.14': + resolution: {integrity: sha512-jZM7IVuZOXa0G110ES8OkxajPTypIKlzlO1K1RIe1auk76ukQRiCg1IRV4HZlWk1GGUbec5hNxsvZa2kU8cb9w==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/hr@0.0.10': + resolution: {integrity: sha512-3AA4Yjgl3zEid/KVx6uf6TuLJHVZvUc2cG9Wm9ZpWeAX4ODA+8g9HyuC0tfnjbRsVMhMcCGiECuWWXINi+60vA==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/html@0.0.10': + resolution: {integrity: sha512-06uiuSKJBWQJfhCKv4MPupELei4Lepyz9Sth7Yq7Fq29CAeB1ejLgKkGqn1I+FZ72hQxPLdYF4iq4yloKv3JCg==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/img@0.0.10': + resolution: {integrity: sha512-pJ8glJjDNaJ53qoM95pvX9SK05yh0bNQY/oyBKmxlBDdUII6ixuMc3SCwYXPMl+tgkQUyDgwEBpSTrLAnjL3hA==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/link@0.0.10': + resolution: {integrity: sha512-tva3wvAWSR10lMJa9fVA09yRn7pbEki0ZZpHE6GD1jKbFhmzt38VgLO9B797/prqoDZdAr4rVK7LJFcdPx3GwA==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/markdown@0.0.12': + resolution: {integrity: sha512-wsuvj1XAb6O63aizCLNEeqVgKR3oFjAwt9vjfg2y2oh4G1dZeo8zonZM2x1fmkEkBZhzwSHraNi70jSXhA3A9w==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/preview@0.0.11': + resolution: {integrity: sha512-7O/CT4b16YlSGrj18htTPx3Vbhu2suCGv/cSe5c+fuSrIM/nMiBSZ3Js16Vj0XJbAmmmlVmYFZw9L20wXJ+LjQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/render@0.0.17': + resolution: {integrity: sha512-xBQ+/73+WsGuXKY7r1U73zMBNV28xdV0cp9cFjhNYipBReDHhV97IpA6v7Hl0dDtDzt+yS/72dY5vYXrF1v8NA==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.2.0 + react-dom: ^18.2.0 + + '@react-email/render@1.0.1': + resolution: {integrity: sha512-W3gTrcmLOVYnG80QuUp22ReIT/xfLsVJ+n7ghSlG2BITB8evNABn1AO2rGQoXuK84zKtDAlxCdm3hRyIpZdGSA==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/row@0.0.10': + resolution: {integrity: sha512-jPyEhG3gsLX+Eb9U+A30fh0gK6hXJwF4ghJ+ZtFQtlKAKqHX+eCpWlqB3Xschd/ARJLod8WAswg0FB+JD9d0/A==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/section@0.0.14': + resolution: {integrity: sha512-+fYWLb4tPU1A/+GE5J1+SEMA7/wR3V30lQ+OR9t2kAJqNrARDbMx0bLnYnR1QL5TiFRz0pCF05SQUobk6gHEDQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/tailwind@0.1.0': + resolution: {integrity: sha512-qysVUEY+M3SKUvu35XDpzn7yokhqFOT3tPU6Mj/pgc62TL5tQFj6msEbBtwoKs2qO3WZvai0DIHdLhaOxBQSow==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + + '@react-email/text@0.0.10': + resolution: {integrity: sha512-wNAnxeEAiFs6N+SxS0y6wTJWfewEzUETuyS2aZmT00xk50VijwyFRuhm4sYSjusMyshevomFwz5jNISCxRsGWw==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + '@repeaterjs/repeater@3.0.6': resolution: {integrity: sha512-Javneu5lsuhwNCryN+pXH93VPQ8g0dBX7wItHFgYiwQmzE1sVdg5tWHiOgHywzL2W21XQopa7IwIEnNbmeUJYA==} + '@selderee/plugin-htmlparser2@0.11.0': + resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} + '@serverless/dashboard-plugin@7.2.3': resolution: {integrity: sha512-Vu4TKJLEQ5F8ZipfCvd8A/LMIdH8kNGe448sX9mT4/Z0JVUaYmMc3BwkQ+zkNIh3QdBKAhocGn45TYjHV6uPWQ==} engines: {node: '>=12.0'} @@ -2107,6 +2475,15 @@ packages: resolution: {integrity: sha512-4pP0EV3iTsexDx+8PPGAKCQpd/6hsQBaQhqWzU4hqKPHN5epPsxKbvUTIiYIHTxaKt6/kEaqPBpu/ufvfbrRzw==} engines: {node: '>=16.0.0'} + '@socket.io/component-emitter@3.1.2': + resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/helpers@0.5.5': + resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} + '@szmarczak/http-timer@4.0.6': resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} engines: {node: '>=10'} @@ -2126,6 +2503,9 @@ packages: '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + '@types/cookie@0.4.1': + resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==} + '@types/cors@2.8.17': resolution: {integrity: sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==} @@ -2174,12 +2554,21 @@ packages: '@types/node@20.14.2': resolution: {integrity: sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==} + '@types/prop-types@15.7.12': + resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} + '@types/qs@6.9.15': resolution: {integrity: sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==} '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/react-dom@18.3.0': + resolution: {integrity: sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg==} + + '@types/react@18.3.5': + resolution: {integrity: sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA==} + '@types/responselike@1.0.3': resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} @@ -2249,6 +2638,10 @@ packages: resolution: {integrity: sha512-1sHRjqUtZIyTR2m2dS/dJpzS5OcNDpPuUSVDa2PoEgzYVKr4GsqJaYtRaEXXFohvvyh6PkouYCc1rE7jMDWVCA==} engines: {node: '>=16.0.0'} + abbrev@2.0.0: + resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} @@ -2445,6 +2838,10 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + base64id@2.0.0: + resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} + engines: {node: ^4.5.0 || >= 5.9} + bcrypt-pbkdf@1.0.2: resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} @@ -2656,6 +3053,9 @@ packages: resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} engines: {node: '>= 10'} + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + cliui@6.0.0: resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} @@ -2699,6 +3099,14 @@ packages: comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -2723,6 +3131,9 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + constant-case@3.0.4: resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==} @@ -2740,6 +3151,10 @@ packages: cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + cookie@0.4.2: + resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} + engines: {node: '>= 0.6'} + cookie@0.6.0: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} @@ -2798,6 +3213,9 @@ packages: resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + d@1.0.2: resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==} engines: {node: '>=0.12'} @@ -2830,6 +3248,10 @@ packages: debounce@1.2.1: resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} + debounce@2.0.0: + resolution: {integrity: sha512-xRetU6gL1VJbs85Mc4FoEGSjQxzpdxRyFhe3lmWFyy2EzydIcD4xzUvRJMD+NPDfMwKNhxa3PvsIOU32luIWeA==} + engines: {node: '>=18'} + debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -2884,6 +3306,10 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} @@ -2952,6 +3378,19 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.1.0: + resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} + dot-case@3.0.4: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} @@ -2976,6 +3415,11 @@ packages: ecc-jsbn@0.1.2: resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} + editorconfig@1.0.4: + resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==} + engines: {node: '>=14'} + hasBin: true + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -2995,6 +3439,14 @@ packages: end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + engine.io-parser@5.2.3: + resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} + engines: {node: '>=10.0.0'} + + engine.io@6.5.5: + resolution: {integrity: sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==} + engines: {node: '>=10.2.0'} + entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -3044,6 +3496,11 @@ packages: es6-weak-map@2.0.3: resolution: {integrity: sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==} + esbuild@0.19.11: + resolution: {integrity: sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==} + engines: {node: '>=12'} + hasBin: true + esbuild@0.20.2: resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} engines: {node: '>=12'} @@ -3179,6 +3636,9 @@ packages: fast-decode-uri-component@1.0.1: resolution: {integrity: sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==} + fast-deep-equal@2.0.1: + resolution: {integrity: sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -3307,6 +3767,10 @@ packages: for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + foreground-child@3.3.0: + resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + engines: {node: '>=14'} + forever-agent@0.6.1: resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} @@ -3420,6 +3884,15 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob@10.3.4: + resolution: {integrity: sha512-6LFElP3A+i/Q8XQKEvZjkEWEOTgAIALR9AO2rwT8bgPhDd1anmqDJDZ6lLddI4ehxxxR1S5RIqKe1uapMQfYaQ==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported @@ -3581,9 +4054,16 @@ packages: resolution: {integrity: sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==} engines: {node: '>=8'} + html-to-text@9.0.5: + resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==} + engines: {node: '>=14'} + html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + htmlparser2@8.0.2: + resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} @@ -3672,6 +4152,9 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + inquirer@8.2.6: resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==} engines: {node: '>=12.0.0'} @@ -3883,6 +4366,13 @@ packages: isstream@0.1.2: resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} + jackspeak@2.3.6: + resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} + engines: {node: '>=14'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + java-invoke-local@0.0.6: resolution: {integrity: sha512-gZmQKe1QrfkkMjCn8Qv9cpyJFyogTYqkP5WCobX5RNaHsJzIV/6NvAnlnouOcwKr29QrxLGDGcqYuJ+ae98s1A==} hasBin: true @@ -3898,6 +4388,15 @@ packages: jose@5.4.0: resolution: {integrity: sha512-6rpxTHPAQyWMb9A35BroFl1Sp0ST3DpPcm5EVIxZxdH+e0Hv9fwhyB3XLKFUcHNpdSDnETmBfuPPTTlYz5+USw==} + js-beautify@1.15.1: + resolution: {integrity: sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==} + engines: {node: '>=14'} + hasBin: true + + js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + js-string-escape@1.0.1: resolution: {integrity: sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==} engines: {node: '>= 0.8'} @@ -3994,6 +4493,9 @@ packages: resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} engines: {node: '>= 0.6.3'} + leac@0.6.0: + resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -4120,6 +4622,16 @@ packages: markdown-table@3.0.3: resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} + marked@7.0.4: + resolution: {integrity: sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ==} + engines: {node: '>= 16'} + hasBin: true + + md-to-react-email@5.0.2: + resolution: {integrity: sha512-x6kkpdzIzUhecda/yahltfEl53mH26QdWu4abUF9+S0Jgam8P//Ciro8cdhyMHnT5MQUJYrIbO6ORM2UxPiNNA==} + peerDependencies: + react: 18.x + mdast-util-find-and-replace@3.0.1: resolution: {integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==} @@ -4331,6 +4843,10 @@ packages: resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} engines: {node: '>=10'} + minimatch@9.0.1: + resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==} + engines: {node: '>=16 || 14 >=14.17'} + minimatch@9.0.4: resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} engines: {node: '>=16 || 14 >=14.17'} @@ -4343,6 +4859,10 @@ packages: resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} engines: {node: '>=8'} + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + minizlib@2.1.2: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} engines: {node: '>= 8'} @@ -4370,6 +4890,11 @@ packages: mute-stream@0.0.8: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + native-promise-only@0.8.1: resolution: {integrity: sha512-zkVhZUA3y8mbz652WrL5x0fB0ehrBkulWT3TomAQ9iDtyXZvzKeEA6GPxAItBYeNYl5yngKRX612qHOhvMkDeg==} @@ -4386,6 +4911,24 @@ packages: next-tick@1.1.0: resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} + next@14.2.3: + resolution: {integrity: sha512-dowFkFTR8v79NPJO4QsBUtxv0g9BrS/phluVpMAt2ku7H+cbcBJlopXjkWlwxrk/xGqMemr7JkGPGemPrLLX7A==} + engines: {node: '>=18.17.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 + react: ^18.2.0 + react-dom: ^18.2.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + sass: + optional: true + nice-try@1.0.5: resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} @@ -4424,6 +4967,11 @@ packages: engines: {node: '>=10'} hasBin: true + nopt@7.2.1: + resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + hasBin: true + normalize-path@2.1.1: resolution: {integrity: sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==} engines: {node: '>=0.10.0'} @@ -4555,6 +5103,9 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} + package-json-from-dist@1.0.0: + resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} + pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} @@ -4576,6 +5127,9 @@ packages: parse5@7.1.2: resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + parseley@0.12.1: + resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -4620,6 +5174,10 @@ packages: resolution: {integrity: sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==} engines: {node: '>=0.10.0'} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + path-to-regexp@0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} @@ -4630,6 +5188,9 @@ packages: path2@0.1.0: resolution: {integrity: sha512-TX+cz8Jk+ta7IvRy2FAej8rdlbrP0+uBIkP/5DTODez/AuL/vSb30KuAdDxGVREXzn8QfAiu5mJYJ1XjbOhEPA==} + peberminta@0.9.0: + resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} + peek-readable@4.1.0: resolution: {integrity: sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==} engines: {node: '>=8'} @@ -4670,10 +5231,18 @@ packages: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} + postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prismjs@1.29.0: + resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} + engines: {node: '>=6'} + process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -4691,6 +5260,9 @@ packages: property-information@6.5.0: resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} + proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -4764,6 +5336,23 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-email@3.0.1: + resolution: {integrity: sha512-G4Bkx2ULIScy/0Z8nnWywHt0W1iTkaYCdh9rWNuQ3eVZ6B3ttTUDE9uUy3VNQ8dtQbmG0cpt8+XmImw7mMBW6Q==} + engines: {node: '>=18.0.0'} + hasBin: true + + react-promise-suspense@0.3.4: + resolution: {integrity: sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==} + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} @@ -4835,6 +5424,10 @@ packages: require-main-filename@2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + resend@4.0.0: + resolution: {integrity: sha512-rDX0rspl/XcmC2JV2V5obQvRX2arzxXUvNFUDMOv5ObBLR68+7kigCOysb7+dlkb0JE3erhQG0nHrbBt/ZCWIg==} + engines: {node: '>=18'} + resolve-alpn@1.2.1: resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} @@ -4901,6 +5494,9 @@ packages: sax@1.2.1: resolution: {integrity: sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==} + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + scuid@1.1.0: resolution: {integrity: sha512-MuCAyrGZcTLfQoH2XoBlQ8C6bzwN88XT/0slOGz0pn8+gIP85BOAfYa44ZXQUTOwRwPU0QvgU+V+OSajl/59Xg==} @@ -4908,6 +5504,9 @@ packages: resolution: {integrity: sha512-e1QtP3YL5tWww8uKaOCQ18UxIT2laNBXHjV/S2WYCiK4udiv8lkG89KRIoCjUagnAmCBurjF4zEVX2ByBbnCjQ==} hasBin: true + selderee@0.11.0: + resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==} + semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true @@ -5032,6 +5631,17 @@ packages: snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + socket.io-adapter@2.5.5: + resolution: {integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==} + + socket.io-parser@4.2.4: + resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==} + engines: {node: '>=10.0.0'} + + socket.io@4.7.5: + resolution: {integrity: sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==} + engines: {node: '>=10.2.0'} + sort-keys-length@1.0.1: resolution: {integrity: sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==} engines: {node: '>=0.10.0'} @@ -5148,6 +5758,19 @@ packages: resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==} engines: {node: '>=10'} + styled-jsx@5.1.1: + resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + superagent@7.1.6: resolution: {integrity: sha512-gZkVCQR1gy/oUXr+kxJMLDjla434KmSOKbx5iGD30Ql+AkJQ/YlPKECJy2nhqOsHLjGHzoDTXNSjhnvWhzKk7g==} engines: {node: '>=6.4.0 <13 || >=14'} @@ -5556,6 +6179,18 @@ packages: utf-8-validate: optional: true + ws@8.17.1: + resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + xml2js@0.6.2: resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==} engines: {node: '>=4.0.0'} @@ -7627,6 +8262,26 @@ snapshots: '@babel/compat-data@7.24.6': {} + '@babel/core@7.24.5': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.24.6 + '@babel/generator': 7.24.6 + '@babel/helper-compilation-targets': 7.24.6 + '@babel/helper-module-transforms': 7.24.6(@babel/core@7.24.5) + '@babel/helpers': 7.24.6 + '@babel/parser': 7.24.6 + '@babel/template': 7.24.6 + '@babel/traverse': 7.24.6 + '@babel/types': 7.24.6 + convert-source-map: 2.0.0 + debug: 4.3.5(supports-color@5.5.0) + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/core@7.24.6': dependencies: '@ampproject/remapping': 2.3.0 @@ -7698,6 +8353,15 @@ snapshots: dependencies: '@babel/types': 7.24.6 + '@babel/helper-module-transforms@7.24.6(@babel/core@7.24.5)': + dependencies: + '@babel/core': 7.24.5 + '@babel/helper-environment-visitor': 7.24.6 + '@babel/helper-module-imports': 7.24.6 + '@babel/helper-simple-access': 7.24.6 + '@babel/helper-split-export-declaration': 7.24.6 + '@babel/helper-validator-identifier': 7.24.6 + '@babel/helper-module-transforms@7.24.6(@babel/core@7.24.6)': dependencies: '@babel/core': 7.24.6 @@ -7750,6 +8414,10 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.0.1 + '@babel/parser@7.24.5': + dependencies: + '@babel/types': 7.24.6 + '@babel/parser@7.24.6': dependencies: '@babel/types': 7.24.6 @@ -7961,72 +8629,141 @@ snapshots: dependencies: tslib: 2.6.3 + '@esbuild/aix-ppc64@0.19.11': + optional: true + '@esbuild/aix-ppc64@0.20.2': optional: true + '@esbuild/android-arm64@0.19.11': + optional: true + '@esbuild/android-arm64@0.20.2': optional: true + '@esbuild/android-arm@0.19.11': + optional: true + '@esbuild/android-arm@0.20.2': optional: true + '@esbuild/android-x64@0.19.11': + optional: true + '@esbuild/android-x64@0.20.2': optional: true + '@esbuild/darwin-arm64@0.19.11': + optional: true + '@esbuild/darwin-arm64@0.20.2': optional: true + '@esbuild/darwin-x64@0.19.11': + optional: true + '@esbuild/darwin-x64@0.20.2': optional: true + '@esbuild/freebsd-arm64@0.19.11': + optional: true + '@esbuild/freebsd-arm64@0.20.2': optional: true + '@esbuild/freebsd-x64@0.19.11': + optional: true + '@esbuild/freebsd-x64@0.20.2': optional: true + '@esbuild/linux-arm64@0.19.11': + optional: true + '@esbuild/linux-arm64@0.20.2': optional: true + '@esbuild/linux-arm@0.19.11': + optional: true + '@esbuild/linux-arm@0.20.2': optional: true + '@esbuild/linux-ia32@0.19.11': + optional: true + '@esbuild/linux-ia32@0.20.2': optional: true + '@esbuild/linux-loong64@0.19.11': + optional: true + '@esbuild/linux-loong64@0.20.2': optional: true + '@esbuild/linux-mips64el@0.19.11': + optional: true + '@esbuild/linux-mips64el@0.20.2': optional: true + '@esbuild/linux-ppc64@0.19.11': + optional: true + '@esbuild/linux-ppc64@0.20.2': optional: true + '@esbuild/linux-riscv64@0.19.11': + optional: true + '@esbuild/linux-riscv64@0.20.2': optional: true + '@esbuild/linux-s390x@0.19.11': + optional: true + '@esbuild/linux-s390x@0.20.2': optional: true + '@esbuild/linux-x64@0.19.11': + optional: true + '@esbuild/linux-x64@0.20.2': optional: true + '@esbuild/netbsd-x64@0.19.11': + optional: true + '@esbuild/netbsd-x64@0.20.2': optional: true + '@esbuild/openbsd-x64@0.19.11': + optional: true + '@esbuild/openbsd-x64@0.20.2': optional: true + '@esbuild/sunos-x64@0.19.11': + optional: true + '@esbuild/sunos-x64@0.20.2': optional: true + '@esbuild/win32-arm64@0.19.11': + optional: true + '@esbuild/win32-arm64@0.20.2': optional: true + '@esbuild/win32-ia32@0.19.11': + optional: true + '@esbuild/win32-ia32@0.20.2': optional: true + '@esbuild/win32-x64@0.19.11': + optional: true + '@esbuild/win32-x64@0.20.2': optional: true @@ -8731,6 +9468,15 @@ snapshots: '@humanwhocodes/object-schema@2.0.3': {} + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + '@jridgewell/gen-mapping@0.3.5': dependencies: '@jridgewell/set-array': 1.2.1 @@ -8758,6 +9504,35 @@ snapshots: '@kwsites/promise-deferred@1.1.1': {} + '@next/env@14.2.3': {} + + '@next/swc-darwin-arm64@14.2.3': + optional: true + + '@next/swc-darwin-x64@14.2.3': + optional: true + + '@next/swc-linux-arm64-gnu@14.2.3': + optional: true + + '@next/swc-linux-arm64-musl@14.2.3': + optional: true + + '@next/swc-linux-x64-gnu@14.2.3': + optional: true + + '@next/swc-linux-x64-musl@14.2.3': + optional: true + + '@next/swc-win32-arm64-msvc@14.2.3': + optional: true + + '@next/swc-win32-ia32-msvc@14.2.3': + optional: true + + '@next/swc-win32-x64-msvc@14.2.3': + optional: true + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -8770,6 +9545,8 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 + '@one-ini/wasm@0.1.1': {} + '@parcel/watcher-android-arm64@2.4.1': optional: true @@ -8850,8 +9627,136 @@ snapshots: tslib: 2.6.3 webcrypto-core: 1.8.0 + '@pkgjs/parseargs@0.11.0': + optional: true + + '@react-email/body@0.0.10(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/button@0.0.17(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/code-block@0.0.9(react@18.3.1)': + dependencies: + prismjs: 1.29.0 + react: 18.3.1 + + '@react-email/code-inline@0.0.4(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/column@0.0.12(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/components@0.0.25(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@react-email/body': 0.0.10(react@18.3.1) + '@react-email/button': 0.0.17(react@18.3.1) + '@react-email/code-block': 0.0.9(react@18.3.1) + '@react-email/code-inline': 0.0.4(react@18.3.1) + '@react-email/column': 0.0.12(react@18.3.1) + '@react-email/container': 0.0.14(react@18.3.1) + '@react-email/font': 0.0.8(react@18.3.1) + '@react-email/head': 0.0.11(react@18.3.1) + '@react-email/heading': 0.0.14(react@18.3.1) + '@react-email/hr': 0.0.10(react@18.3.1) + '@react-email/html': 0.0.10(react@18.3.1) + '@react-email/img': 0.0.10(react@18.3.1) + '@react-email/link': 0.0.10(react@18.3.1) + '@react-email/markdown': 0.0.12(react@18.3.1) + '@react-email/preview': 0.0.11(react@18.3.1) + '@react-email/render': 1.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-email/row': 0.0.10(react@18.3.1) + '@react-email/section': 0.0.14(react@18.3.1) + '@react-email/tailwind': 0.1.0(react@18.3.1) + '@react-email/text': 0.0.10(react@18.3.1) + react: 18.3.1 + transitivePeerDependencies: + - react-dom + + '@react-email/container@0.0.14(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/font@0.0.8(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/head@0.0.11(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/heading@0.0.14(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/hr@0.0.10(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/html@0.0.10(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/img@0.0.10(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/link@0.0.10(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/markdown@0.0.12(react@18.3.1)': + dependencies: + md-to-react-email: 5.0.2(react@18.3.1) + react: 18.3.1 + + '@react-email/preview@0.0.11(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/render@0.0.17(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + html-to-text: 9.0.5 + js-beautify: 1.15.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-promise-suspense: 0.3.4 + + '@react-email/render@1.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + html-to-text: 9.0.5 + js-beautify: 1.15.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-promise-suspense: 0.3.4 + + '@react-email/row@0.0.10(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/section@0.0.14(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/tailwind@0.1.0(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/text@0.0.10(react@18.3.1)': + dependencies: + react: 18.3.1 + '@repeaterjs/repeater@3.0.6': {} + '@selderee/plugin-htmlparser2@0.11.0': + dependencies: + domhandler: 5.0.3 + selderee: 0.11.0 + '@serverless/dashboard-plugin@7.2.3(supports-color@8.1.1)': dependencies: '@aws-sdk/client-cloudformation': 3.592.0 @@ -9726,6 +10631,15 @@ snapshots: '@smithy/types': 3.3.0 tslib: 2.6.3 + '@socket.io/component-emitter@3.1.2': {} + + '@swc/counter@0.1.3': {} + + '@swc/helpers@0.5.5': + dependencies: + '@swc/counter': 0.1.3 + tslib: 2.6.3 + '@szmarczak/http-timer@4.0.6': dependencies: defer-to-connect: 2.0.1 @@ -9750,6 +10664,8 @@ snapshots: dependencies: '@types/node': 20.14.1 + '@types/cookie@0.4.1': {} + '@types/cors@2.8.17': dependencies: '@types/node': 20.14.1 @@ -9806,10 +10722,21 @@ snapshots: dependencies: undici-types: 5.26.5 + '@types/prop-types@15.7.12': {} + '@types/qs@6.9.15': {} '@types/range-parser@1.2.7': {} + '@types/react-dom@18.3.0': + dependencies: + '@types/react': 18.3.5 + + '@types/react@18.3.5': + dependencies: + '@types/prop-types': 15.7.12 + csstype: 3.1.3 + '@types/responselike@1.0.3': dependencies: '@types/node': 20.14.2 @@ -9908,6 +10835,8 @@ snapshots: '@whatwg-node/fetch': 0.9.18 tslib: 2.6.3 + abbrev@2.0.0: {} + abort-controller@3.0.0: dependencies: event-target-shim: 5.0.1 @@ -10157,6 +11086,8 @@ snapshots: base64-js@1.5.1: {} + base64id@2.0.0: {} + bcrypt-pbkdf@1.0.2: dependencies: tweetnacl: 0.14.5 @@ -10438,6 +11369,8 @@ snapshots: cli-width@3.0.0: {} + client-only@0.0.1: {} + cliui@6.0.0: dependencies: string-width: 4.2.3 @@ -10490,6 +11423,10 @@ snapshots: comma-separated-tokens@2.0.3: {} + commander@10.0.1: {} + + commander@11.1.0: {} + commander@2.20.3: {} commander@4.1.1: {} @@ -10509,6 +11446,11 @@ snapshots: concat-map@0.0.1: {} + config-chain@1.1.13: + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + constant-case@3.0.4: dependencies: no-case: 3.0.4 @@ -10525,6 +11467,8 @@ snapshots: cookie-signature@1.0.6: {} + cookie@0.4.2: {} + cookie@0.6.0: {} cookiejar@2.1.4: {} @@ -10587,6 +11531,8 @@ snapshots: mdn-data: 2.0.30 source-map-js: 1.2.0 + csstype@3.1.3: {} + d@1.0.2: dependencies: es5-ext: 0.10.64 @@ -10622,6 +11568,8 @@ snapshots: debounce@1.2.1: {} + debounce@2.0.0: {} + debug@2.6.9: dependencies: ms: 2.0.0 @@ -10690,6 +11638,8 @@ snapshots: deep-is@0.1.4: {} + deepmerge@4.3.1: {} + defaults@1.0.4: dependencies: clone: 1.0.4 @@ -10751,6 +11701,24 @@ snapshots: dependencies: esutils: 2.0.3 + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.1.0: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + dot-case@3.0.4: dependencies: no-case: 3.0.4 @@ -10774,6 +11742,13 @@ snapshots: jsbn: 0.1.1 safer-buffer: 2.1.2 + editorconfig@1.0.4: + dependencies: + '@one-ini/wasm': 0.1.1 + commander: 10.0.1 + minimatch: 9.0.1 + semver: 7.6.2 + ee-first@1.1.1: {} electron-to-chromium@1.4.790: {} @@ -10788,6 +11763,25 @@ snapshots: dependencies: once: 1.4.0 + engine.io-parser@5.2.3: {} + + engine.io@6.5.5: + dependencies: + '@types/cookie': 0.4.1 + '@types/cors': 2.8.17 + '@types/node': 20.14.2 + accepts: 1.3.8 + base64id: 2.0.0 + cookie: 0.4.2 + cors: 2.8.5 + debug: 4.3.5(supports-color@5.5.0) + engine.io-parser: 5.2.3 + ws: 8.17.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + entities@4.5.0: {} error-ex@1.3.2: @@ -10899,6 +11893,32 @@ snapshots: es6-iterator: 2.0.3 es6-symbol: 3.1.4 + esbuild@0.19.11: + optionalDependencies: + '@esbuild/aix-ppc64': 0.19.11 + '@esbuild/android-arm': 0.19.11 + '@esbuild/android-arm64': 0.19.11 + '@esbuild/android-x64': 0.19.11 + '@esbuild/darwin-arm64': 0.19.11 + '@esbuild/darwin-x64': 0.19.11 + '@esbuild/freebsd-arm64': 0.19.11 + '@esbuild/freebsd-x64': 0.19.11 + '@esbuild/linux-arm': 0.19.11 + '@esbuild/linux-arm64': 0.19.11 + '@esbuild/linux-ia32': 0.19.11 + '@esbuild/linux-loong64': 0.19.11 + '@esbuild/linux-mips64el': 0.19.11 + '@esbuild/linux-ppc64': 0.19.11 + '@esbuild/linux-riscv64': 0.19.11 + '@esbuild/linux-s390x': 0.19.11 + '@esbuild/linux-x64': 0.19.11 + '@esbuild/netbsd-x64': 0.19.11 + '@esbuild/openbsd-x64': 0.19.11 + '@esbuild/sunos-x64': 0.19.11 + '@esbuild/win32-arm64': 0.19.11 + '@esbuild/win32-ia32': 0.19.11 + '@esbuild/win32-x64': 0.19.11 + esbuild@0.20.2: optionalDependencies: '@esbuild/aix-ppc64': 0.20.2 @@ -11125,6 +12145,8 @@ snapshots: fast-decode-uri-component@1.0.1: {} + fast-deep-equal@2.0.1: {} + fast-deep-equal@3.1.3: {} fast-glob@3.3.2: @@ -11260,6 +12282,11 @@ snapshots: dependencies: is-callable: 1.2.7 + foreground-child@3.3.0: + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + forever-agent@0.6.1: {} form-data@2.3.3: @@ -11383,6 +12410,23 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@10.3.4: + dependencies: + foreground-child: 3.3.0 + jackspeak: 2.3.6 + minimatch: 9.0.4 + minipass: 7.1.2 + path-scurry: 1.11.1 + + glob@10.4.5: + dependencies: + foreground-child: 3.3.0 + jackspeak: 3.4.3 + minimatch: 9.0.4 + minipass: 7.1.2 + package-json-from-dist: 1.0.0 + path-scurry: 1.11.1 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -11621,8 +12665,23 @@ snapshots: hexoid@1.0.0: {} + html-to-text@9.0.5: + dependencies: + '@selderee/plugin-htmlparser2': 0.11.0 + deepmerge: 4.3.1 + dom-serializer: 2.0.0 + htmlparser2: 8.0.2 + selderee: 0.11.0 + html-void-elements@3.0.0: {} + htmlparser2@8.0.2: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.1.0 + entities: 4.5.0 + http-cache-semantics@4.1.1: {} http-errors@2.0.0: @@ -11707,6 +12766,8 @@ snapshots: inherits@2.0.4: {} + ini@1.3.8: {} + inquirer@8.2.6: dependencies: ansi-escapes: 4.3.2 @@ -11898,6 +12959,18 @@ snapshots: isstream@0.1.2: {} + jackspeak@2.3.6: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + java-invoke-local@0.0.6: {} jiti@1.21.1: {} @@ -11906,6 +12979,16 @@ snapshots: jose@5.4.0: {} + js-beautify@1.15.1: + dependencies: + config-chain: 1.1.13 + editorconfig: 1.0.4 + glob: 10.4.5 + js-cookie: 3.0.5 + nopt: 7.2.1 + + js-cookie@3.0.5: {} + js-string-escape@1.0.1: {} js-tokens@4.0.0: {} @@ -12000,6 +13083,8 @@ snapshots: dependencies: readable-stream: 2.3.8 + leac@0.6.0: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -12130,6 +13215,13 @@ snapshots: markdown-table@3.0.3: {} + marked@7.0.4: {} + + md-to-react-email@5.0.2(react@18.3.1): + dependencies: + marked: 7.0.4 + react: 18.3.1 + mdast-util-find-and-replace@3.0.1: dependencies: '@types/mdast': 4.0.4 @@ -12503,6 +13595,10 @@ snapshots: dependencies: brace-expansion: 2.0.1 + minimatch@9.0.1: + dependencies: + brace-expansion: 2.0.1 + minimatch@9.0.4: dependencies: brace-expansion: 2.0.1 @@ -12513,6 +13609,8 @@ snapshots: minipass@5.0.0: {} + minipass@7.1.2: {} + minizlib@2.1.2: dependencies: minipass: 3.3.6 @@ -12534,6 +13632,8 @@ snapshots: mute-stream@0.0.8: {} + nanoid@3.3.7: {} + native-promise-only@0.8.1: {} natural-compare@1.4.0: {} @@ -12553,6 +13653,31 @@ snapshots: next-tick@1.1.0: {} + next@14.2.3(@babel/core@7.24.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@next/env': 14.2.3 + '@swc/helpers': 0.5.5 + busboy: 1.6.0 + caniuse-lite: 1.0.30001628 + graceful-fs: 4.2.11 + postcss: 8.4.31 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + styled-jsx: 5.1.1(@babel/core@7.24.5)(react@18.3.1) + optionalDependencies: + '@next/swc-darwin-arm64': 14.2.3 + '@next/swc-darwin-x64': 14.2.3 + '@next/swc-linux-arm64-gnu': 14.2.3 + '@next/swc-linux-arm64-musl': 14.2.3 + '@next/swc-linux-x64-gnu': 14.2.3 + '@next/swc-linux-x64-musl': 14.2.3 + '@next/swc-win32-arm64-msvc': 14.2.3 + '@next/swc-win32-ia32-msvc': 14.2.3 + '@next/swc-win32-x64-msvc': 14.2.3 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + nice-try@1.0.5: {} no-case@3.0.4: @@ -12593,6 +13718,10 @@ snapshots: touch: 3.1.1 undefsafe: 2.0.5 + nopt@7.2.1: + dependencies: + abbrev: 2.0.0 + normalize-path@2.1.1: dependencies: remove-trailing-separator: 1.1.0 @@ -12731,6 +13860,8 @@ snapshots: p-try@2.2.0: {} + package-json-from-dist@1.0.0: {} + pako@1.0.11: {} param-case@3.0.4: @@ -12759,6 +13890,11 @@ snapshots: dependencies: entities: 4.5.0 + parseley@0.12.1: + dependencies: + leac: 0.6.0 + peberminta: 0.9.0 + parseurl@1.3.3: {} pascal-case@3.1.2: @@ -12796,12 +13932,19 @@ snapshots: dependencies: path-root-regex: 0.1.2 + path-scurry@1.11.1: + dependencies: + lru-cache: 10.2.2 + minipass: 7.1.2 + path-to-regexp@0.1.7: {} path-type@4.0.0: {} path2@0.1.0: {} + peberminta@0.9.0: {} + peek-readable@4.1.0: {} pend@1.2.0: {} @@ -12830,8 +13973,16 @@ snapshots: possible-typed-array-names@1.0.0: {} + postcss@8.4.31: + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.1 + source-map-js: 1.2.0 + prelude-ls@1.2.1: {} + prismjs@1.29.0: {} + process-nextick-args@2.0.1: {} process-utils@4.0.0: @@ -12849,6 +14000,8 @@ snapshots: property-information@6.5.0: {} + proto-list@1.2.4: {} + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -12906,6 +14059,47 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-email@3.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/core': 7.24.5 + '@babel/parser': 7.24.5 + chalk: 4.1.2 + chokidar: 3.6.0 + commander: 11.1.0 + debounce: 2.0.0 + esbuild: 0.19.11 + glob: 10.3.4 + log-symbols: 4.1.0 + mime-types: 2.1.35 + next: 14.2.3(@babel/core@7.24.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + normalize-path: 3.0.0 + ora: 5.4.1 + socket.io: 4.7.5 + transitivePeerDependencies: + - '@opentelemetry/api' + - '@playwright/test' + - babel-plugin-macros + - bufferutil + - react + - react-dom + - sass + - supports-color + - utf-8-validate + + react-promise-suspense@0.3.4: + dependencies: + fast-deep-equal: 2.0.1 + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 @@ -13032,6 +14226,13 @@ snapshots: require-main-filename@2.0.0: {} + resend@4.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@react-email/render': 0.0.17(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + transitivePeerDependencies: + - react + - react-dom + resolve-alpn@1.2.1: {} resolve-from@4.0.0: {} @@ -13096,12 +14297,20 @@ snapshots: sax@1.2.1: {} + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + scuid@1.1.0: {} seek-bzip@1.0.6: dependencies: commander: 2.20.3 + selderee@0.11.0: + dependencies: + parseley: 0.12.1 + semver@5.7.2: {} semver@6.3.1: {} @@ -13346,6 +14555,36 @@ snapshots: dot-case: 3.0.4 tslib: 2.6.3 + socket.io-adapter@2.5.5: + dependencies: + debug: 4.3.5(supports-color@5.5.0) + ws: 8.17.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + socket.io-parser@4.2.4: + dependencies: + '@socket.io/component-emitter': 3.1.2 + debug: 4.3.5(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + + socket.io@4.7.5: + dependencies: + accepts: 1.3.8 + base64id: 2.0.0 + cors: 2.8.5 + debug: 4.3.5(supports-color@5.5.0) + engine.io: 6.5.5 + socket.io-adapter: 2.5.5 + socket.io-parser: 4.2.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + sort-keys-length@1.0.1: dependencies: sort-keys: 1.1.2 @@ -13473,6 +14712,13 @@ snapshots: '@tokenizer/token': 0.3.0 peek-readable: 4.1.0 + styled-jsx@5.1.1(@babel/core@7.24.5)(react@18.3.1): + dependencies: + client-only: 0.0.1 + react: 18.3.1 + optionalDependencies: + '@babel/core': 7.24.5 + superagent@7.1.6(supports-color@8.1.1): dependencies: component-emitter: 1.3.1 @@ -13932,6 +15178,8 @@ snapshots: ws@8.17.0: {} + ws@8.17.1: {} + xml2js@0.6.2: dependencies: sax: 1.2.1 diff --git a/server/serverless.yml b/server/serverless.yml index d4ae340..8b21ced 100644 --- a/server/serverless.yml +++ b/server/serverless.yml @@ -26,6 +26,7 @@ provider: USER_AUTH_SECRET: ${ssm:/epc--user-auth-secret} PASSWORDLESS_SECRET: ${ssm:/epc--passwordless-secret} BREVO_API_KEY: ${ssm:/epc--brevo-api-key} + RESEND_API_KEY: ${ssm:/epc--resend-api-key} functions: api: diff --git a/server/src/api/routes/auth.ts b/server/src/api/routes/auth.ts index 36540a4..21fce57 100644 --- a/server/src/api/routes/auth.ts +++ b/server/src/api/routes/auth.ts @@ -38,6 +38,7 @@ export const sendMagicLink = t.procedure userFromEmail(email: $email) { id email + firstName } } `), @@ -51,6 +52,7 @@ export const sendMagicLink = t.procedure await passwordlessSendMagicLink({ userId: u.id, email: u.email, + firstName: u.firstName ?? undefined, redirect, }).catch((e) => { throw err('BAD_REQUEST', 'MAGIC_LINK_FAILED', e); diff --git a/server/src/api/routes/register.ts b/server/src/api/routes/register.ts index 221b697..682d1a5 100644 --- a/server/src/api/routes/register.ts +++ b/server/src/api/routes/register.ts @@ -4,8 +4,8 @@ import { err, t } from '../trpc'; import { graph } from '@@/db/graph'; import { graphql } from '@@/db/lib/utilities'; import { signReferralToken, signToken } from '@@/auth/sign'; -import { sendRegistrationEmail } from '@@/email/send'; import { verifyReferralToken } from '@@/auth/verify'; +import { emails } from '@@/email'; export const checkReferral = t.procedure .input(z.object({ email: z.string() })) @@ -32,9 +32,8 @@ export const checkReferral = t.procedure }); // send email - await sendRegistrationEmail(parsedEmail, token).catch((e) => { - throw err('INTERNAL_SERVER_ERROR', 'FAILED_TO_SEND_EMAIL', e); - }); + const success = await emails.emailRegistration(parsedEmail, { token }); + if (!success) throw err('INTERNAL_SERVER_ERROR', 'EMAIL_SEND_FAILED'); }); export const verifyToken = t.procedure diff --git a/server/src/auth/passkeys.ts b/server/src/auth/passkeys.ts index 21a5388..4bed662 100644 --- a/server/src/auth/passkeys.ts +++ b/server/src/auth/passkeys.ts @@ -1,4 +1,5 @@ import { EMAIL_LOGIN_EXPIRE } from '@@/CONSTANTS'; +import { emails } from '@@/email'; import { isDev, siteDomain } from '@@/util/dev'; import { PasswordlessClient } from '@passwordlessdev/passwordless-nodejs'; import axios from 'axios'; @@ -14,6 +15,7 @@ export const passwordless = new PasswordlessClient(PASSWORDLESS_SECRET!, { export async function passwordlessSendMagicLink(p: { email: string; userId: string; + firstName?: string; redirect?: string; }) { const params = { @@ -27,22 +29,25 @@ export async function passwordlessSendMagicLink(p: { timeToLive: EMAIL_LOGIN_EXPIRE, }; - if (isDev) { - // generate fake login email for dev environments - const { data } = await axios.post( + // get validation token + const { data } = await axios + .post( PASSWORDLESS_API + '/signin/generate-token', { userId: params.userId }, { headers: { ApiSecret: PASSWORDLESS_SECRET } } - ); - const out = { - ...params, - urlTemplate: undefined, - url: params.urlTemplate.replace('$TOKEN', data?.token), - }; - console.log('MAGIC LINK EMAIL\n', out); - return; - } + ) + .catch(() => ({ data: null })); + if (!data?.token) throw new Error(); + const url = params.urlTemplate.replace('$TOKEN', data.token); + + const success = await emails.emailLogin(p.email, { + url, + firstName: p.firstName, + }); + if (success) return; + + // use passwordless as a fallback sender await axios.post(PASSWORDLESS_API + '/magic-links/send', params, { headers: { ApiSecret: PASSWORDLESS_SECRET }, }); diff --git a/server/src/db/schema/CMSFile/functions.ts b/server/src/db/schema/CMSFile/functions.ts index 70a9f92..21d8e30 100644 --- a/server/src/db/schema/CMSFile/functions.ts +++ b/server/src/db/schema/CMSFile/functions.ts @@ -8,19 +8,9 @@ import { handle as h, loggedIn, } from '@@/db/lib/utilities'; -import { - createS3Folder, - deleteS3File, - deleteS3Folder, - doesFileExist, - getS3UploadUrl, - getS3Uri, - getSignedS3Url, - listS3Files, - moveS3File, -} from '@@/s3/s3'; import { BUCKET } from '@@/s3/files'; +import { s3 } from '@@/s3'; const { scoped, scopeDiff } = getTypedScopeFunctions(); @@ -34,7 +24,7 @@ export const getCmsFiles = h( } // get files list - const resp = await listS3Files(BUCKET, root ?? undefined, { + const resp = await s3.listFiles(BUCKET, root ?? undefined, { max: max ?? DEFAULT_MAX_FILES_LIST_LIMIT, start: startAfter ?? undefined, }); @@ -57,7 +47,7 @@ export const getCmsFiles = h( export const getCmsFilePresign = h( loggedIn(), async ({ args: { path } }) => { - return getSignedS3Url({ bucket: BUCKET, path }); + return s3.getSignedUrl({ bucket: BUCKET, path }); } ); @@ -79,17 +69,19 @@ export const cmsFileUpload = h( }; // presign upload url - const { data, error } = await getS3UploadUrl(fp); + const { data, error } = await s3.getUploadUrl(fp); if (error || !data) throw err('PRESIGN_FAILED'); // create folder if it doesn't already exist - const folder_exists = await doesFileExist({ - bucket: BUCKET, - path: root, - }).catch(() => null); - if (folder_exists === false) await createS3Folder(BUCKET, root); + const folder_exists = await s3 + .doesFileExist({ + bucket: BUCKET, + path: root, + }) + .catch(() => null); + if (folder_exists === false) await s3.createFolder(BUCKET, root); - return { uri: getS3Uri(fp), url: data }; + return { uri: s3.getUri(fp), url: data }; } ); @@ -103,7 +95,7 @@ export const cmsFileCreateFolder = h< if (!root.match(/^\w/)) throw err('BAD_FOLDER_NAME'); if (!root.match(/\/$/)) throw err('ROOT_FOLDER_MISSING_TRAILING_SLASH'); - await createS3Folder(BUCKET, root).catch((e) => { + await s3.createFolder(BUCKET, root).catch((e) => { throw err('S3_ERROR', undefined, e); }); @@ -117,12 +109,13 @@ export const cmsFileMove = h( let finished = 0; await Promise.all( changes.map(async ({ path, newPath }) => { - await moveS3File({ - bucket: BUCKET, - path, - newPath, - deleteOld: copy !== undefined ? !copy : true, - }) + await s3 + .moveFile({ + bucket: BUCKET, + path, + newPath, + deleteOld: copy !== undefined ? !copy : true, + }) .then(() => finished++) .catch((e) => { err('', undefined, e); @@ -141,10 +134,10 @@ export const cmsFileDelete = h( paths.map(async (path) => { path = path.trim(); if (path.at(-1) === '/') { - const { errors } = await deleteS3Folder({ bucket: BUCKET, path }); + const { errors } = await s3.deleteFolder({ bucket: BUCKET, path }); if (errors.length) throw err('FAILED_ITEMS', JSON.stringify(errors)); } else { - await deleteS3File({ bucket: BUCKET, path }).catch(() => {}); + await s3.deleteFile({ bucket: BUCKET, path }).catch(() => {}); } }) ); diff --git a/server/src/db/schema/CMSImage/functions.ts b/server/src/db/schema/CMSImage/functions.ts index 25767e3..49221f5 100644 --- a/server/src/db/schema/CMSImage/functions.ts +++ b/server/src/db/schema/CMSImage/functions.ts @@ -11,14 +11,7 @@ import type { DBCmsImage } from './source'; import type { DBType } from '@@/db/lib/Model'; import { BUCKET, PATH, checkImageType } from '@@/s3/images'; -import { - deleteS3File, - doesFileExist, - getS3Uri, - getSignedS3Url, - getS3UploadUrl, - parseS3Uri, -} from '@@/s3/s3'; +import { s3 } from '@@/s3'; import { randomUUID as uuid } from 'node:crypto'; @@ -83,7 +76,7 @@ export const cmsImageUpload = h( bucket: BUCKET, path: PATH + `${id}.${imageType.ext}`, }; - const uri = getS3Uri(fp); + const uri = s3.getUri(fp); const name = fileName.trim().match(/(.*)\.[^\.]+$/)?.[1]; // create DB entry @@ -104,7 +97,7 @@ export const cmsImageUpload = h( }); // presign upload url - const { data, error } = await getS3UploadUrl(fp); + const { data, error } = await s3.getUploadUrl(fp); if (error || !data) throw err('PRESIGN_FAILED'); return { id, url: data }; @@ -119,9 +112,9 @@ export const cmsImageConfirm = h( if (!img) throw err('INVALID_ID'); // check to make sure the file exists - const fp = parseS3Uri(img.uri); + const fp = s3.parseUri(img.uri); if (!fp) throw err('INVALID_URI'); - const exists = await doesFileExist(fp).catch((err) => { + const exists = await s3.doesFileExist(fp).catch((err) => { throw err('FAILED_TO_CONFIRM', err); }); if (!exists) throw err('FILE_NOT_FOUND'); @@ -159,10 +152,10 @@ export const cmsImageDelete = h( const img = await sources.cms.image.get(id); if (!img) throw err('IMAGE_NOT_FOUND'); - const fp = parseS3Uri(img.uri); + const fp = s3.parseUri(img.uri); if (!fp) throw err('INVALID_URI'); - await deleteS3File(fp).catch((error) => { + await s3.deleteFile(fp).catch((error) => { throw err('FAILED_TO_DELETE', undefined, error); }); @@ -180,9 +173,9 @@ export const cmsImageDeleteMultiple = h< // attempt to delete images as well await Promise.all( imgs.map(async (img) => { - const fp = parseS3Uri(img?.uri ?? ''); + const fp = s3.parseUri(img?.uri ?? ''); if (!fp) return; - await deleteS3File(fp).catch((error) => { + await s3.deleteFile(fp).catch((error) => { throw err('FAILED_TO_DELETE', undefined, error); }); }) @@ -201,9 +194,9 @@ export const cmsImageDeleteUnconfirmed = h< // attempt to delete images as well await Promise.all( images.map(async (img) => { - const fp = parseS3Uri(img.uri); + const fp = s3.parseUri(img.uri); if (!fp) throw err('INVALID_URI'); - await deleteS3File(fp).catch((error) => { + await s3.deleteFile(fp).catch((error) => { throw err('FAILED_TO_DELETE', undefined, error); }); }) @@ -237,7 +230,7 @@ export const getCmsImageConfirmed = h( export const getCmsImageUrl = h( async ({ parent: { uri } }) => { - const url = await getSignedS3Url(uri); + const url = await s3.getSignedUrl(uri); if (!url) throw err('FAILED_TO_SIGN_URL'); return url; diff --git a/server/src/email/brevo.ts b/server/src/email/brevo.ts new file mode 100644 index 0000000..ffc8452 --- /dev/null +++ b/server/src/email/brevo.ts @@ -0,0 +1,35 @@ +import { + SendSmtpEmail, + SendSmtpEmailSender, + TransactionalEmailsApi, +} from '@getbrevo/brevo'; + +const { BREVO_API_KEY } = process.env; + +let brevo = new TransactionalEmailsApi(); +brevo.setApiKey(0, BREVO_API_KEY as string); + +const sender: SendSmtpEmailSender = { + name: 'Elm Point', + email: 'noreply@elmpoint.xyz', +}; + +export async function sendRawEmail(props: { + to: string | string[]; + subject: string; + html: string; + text: string; +}) { + const { to, subject, html, text } = props; + + const email = new SendSmtpEmail(); + email.sender = sender; + email.to = Array.isArray(to) + ? to.map((email) => ({ email })) + : [{ email: to }]; + email.subject = subject; + email.htmlContent = html; + email.textContent = text; + + await brevo.sendTransacEmail(email); +} diff --git a/server/src/email/components/index.tsx b/server/src/email/components/index.tsx new file mode 100644 index 0000000..b36b7f9 --- /dev/null +++ b/server/src/email/components/index.tsx @@ -0,0 +1,117 @@ +import React, { forwardRef, ReactNode } from 'react'; + +import { + Body, + Button as Button0, + Container, + Head, + Heading, + Hr, + Html, + Img, + Link as Link0, + Section, + Tailwind, +} from '@react-email/components'; + +type Children = { children?: ReactNode }; +type ClassNames = { className?: string }; +export const clx = (...s: (string | unknown)[]) => + s.filter((it) => typeof it === 'string').join(' '); + +// ------------------------------------------------------ + +type WrapperProps = { footer?: ReactNode } & Children; +export const Wrapper = forwardRef( + ({ children, footer }, ref) => ( + + + + + +
+
+ Elm Point +
+ +
+ + {children} +
+
+ {footer} + +
+ + ) +); + +export const Prose = forwardRef( + ({ children, className }, ref) => ( +
+ {children} + {/* */} +
+ ) +); + +export const Footer = forwardRef( + ({ children, className }, ref) => ( +
+ {children} +
+ ) +); + +export const Title = forwardRef( + ({ children, className }, ref) => ( + + {children} + {/* */} + + ) +); + +export const HR = forwardRef( + ({ className }, ref) => ( + <> +
+ + ) +); + +export const Button = forwardRef< + HTMLAnchorElement, + React.ComponentPropsWithoutRef +>(({ children, className, ...props }, ref) => ( + + {children} + +)); + +export const Link = forwardRef< + HTMLAnchorElement, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); diff --git a/server/src/email/index.ts b/server/src/email/index.ts new file mode 100644 index 0000000..157dce3 --- /dev/null +++ b/server/src/email/index.ts @@ -0,0 +1,6 @@ +export * as resend from './resend'; +export * as brevo from './brevo'; + +export { Senders } from './senders'; + +export * as emails from './templates'; diff --git a/server/src/email/resend.tsx b/server/src/email/resend.tsx new file mode 100644 index 0000000..918dd85 --- /dev/null +++ b/server/src/email/resend.tsx @@ -0,0 +1,60 @@ +import { CreateEmailOptions, Resend } from 'resend'; + +import { Senders } from './senders'; +import { isDev } from '@@/util/dev'; +import { render } from '@react-email/components'; +import { brevo } from '.'; + +const resend = new Resend(process.env.RESEND_API_KEY ?? 'DEVELOPMENT'); + +export async function send( + { ...props }: { from: Senders } & CreateEmailOptions, + options?: { fallback?: boolean } +) { + if (isDev) return localSend(props.react, props.subject); + + const { error } = await resend.emails.send({ + ...props, + text: props.text ?? (await render(<>{props.react}, { plainText: true })), + }); + if (error) { + if (error.name.includes('invalid') || error.name.includes('missing')) + return false; + + // run fallback if requested + if (!options?.fallback) return false; + return brevoFallback(props.to, props.subject, props.react); + } + return true; +} + +function localSend(content: React.ReactNode, title?: string) { + return catchTF(async () => { + const text = await render(<>{content}, { plainText: true }); + console.log(`${title || 'EMAIL CONTENTS'}\n`, text); + }); +} + +function brevoFallback( + to: string | string[], + subject: string, + content: React.ReactNode +) { + return catchTF(async () => { + const html = await render(<>{content}); + const text = await render(<>{content}, { plainText: true }); + + await brevo.sendRawEmail({ to, subject, html, text }); + }); +} + +// -------------------------------------- + +async function catchTF(cb: (...p: any[]) => any): Promise { + try { + const out = await cb(); + return out ?? true; + } catch (_) { + return false; + } +} diff --git a/server/src/email/send.ts b/server/src/email/send.ts deleted file mode 100644 index af92c28..0000000 --- a/server/src/email/send.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { - SendSmtpEmail, - SendSmtpEmailSender, - TransactionalEmailsApi, -} from '@getbrevo/brevo'; -import qs from 'qs'; -import { isDev, siteDomain } from '@@/util/dev'; - -const { BREVO_API_KEY } = process.env; - -let brevo = new TransactionalEmailsApi(); -brevo.setApiKey(0, BREVO_API_KEY as string); - -const sender: SendSmtpEmailSender = { - name: 'Elm Point', - email: 'no-reply@elmpoint.xyz', -}; - -export async function sendRegistrationEmail( - emailAddress: string, - token: string -) { - const email = new SendSmtpEmail(); - email.sender = sender; - email.to = [{ email: emailAddress }]; - email.templateId = 1; - email.params = { - URL: `${siteDomain}/auth/activate?${qs.stringify({ token })}`, - }; - - if (isDev) { - console.log('REGISTRATION EMAIL\n', email.params); - return; - } - - await brevo.sendTransacEmail(email); -} diff --git a/server/src/email/senders.ts b/server/src/email/senders.ts new file mode 100644 index 0000000..9081a96 --- /dev/null +++ b/server/src/email/senders.ts @@ -0,0 +1,4 @@ +export enum Senders { + DEFAULT = 'Elm Point ', + AUTH = 'Elm Point ', +} diff --git a/server/src/email/templates/index.ts b/server/src/email/templates/index.ts new file mode 100644 index 0000000..32f7024 --- /dev/null +++ b/server/src/email/templates/index.ts @@ -0,0 +1,2 @@ +export * from './registration'; +export * from './login'; diff --git a/server/src/email/templates/login.tsx b/server/src/email/templates/login.tsx new file mode 100644 index 0000000..822acb1 --- /dev/null +++ b/server/src/email/templates/login.tsx @@ -0,0 +1,47 @@ +import { Button, Footer, Link, Prose, Title, Wrapper } from '../components'; +import { send } from '../resend'; +import { Senders } from '../senders'; + +export async function emailLogin( + to: string, + { url, firstName }: { url: string; firstName?: string } +) { + return send({ + to, + from: Senders.AUTH, + subject: SUBJECT, + react: Content({ url, firstName }), + }); +} + +const SUBJECT = 'Login link for Elm Point'; +function Content({ url, firstName }: { url: string; firstName?: string }) { + return ( + + + Sign in to your account +

Hi{firstName ? ' ' + firstName : ''}!

+

+ Click the link below to sign in to your account. +

+
+ + + } + footer={ +
+

This is an automated message.

+

+ Click this link if the button above didn’t work: +
+ + {url} + +

+
+ } + /> + ); +} diff --git a/server/src/email/templates/registration.tsx b/server/src/email/templates/registration.tsx new file mode 100644 index 0000000..469bcad --- /dev/null +++ b/server/src/email/templates/registration.tsx @@ -0,0 +1,52 @@ +import qs from 'qs'; +import { Button, Footer, Link, Prose, Title, Wrapper } from '../components'; +import { send } from '../resend'; +import { Senders } from '../senders'; +import { siteDomain } from '@@/util/dev'; + +/** verify a user's email address for registration. */ +export async function emailRegistration( + emailAddress: string, + { token }: { token: string } +) { + const url = `${siteDomain}/auth/activate?${qs.stringify({ token })}`; + + return send( + { + to: emailAddress, + from: Senders.AUTH, + subject: SUBJECT, + react: Content({ url }), + }, + { fallback: true } + ); +} + +const SUBJECT = 'Verify your email address'; +function Content({ url }: { url: string }) { + return ( + + + Verify your email +

Click the link below to finish creating your account.

+
+ + + } + footer={ +
+

This is an automated message.

+

+ Click this link if the button above didn’t work: +
+ + {url} + +

+
+ } + /> + ); +} diff --git a/server/src/ics/cache.ts b/server/src/ics/cache.ts index 350b299..6c0fcda 100644 --- a/server/src/ics/cache.ts +++ b/server/src/ics/cache.ts @@ -4,13 +4,8 @@ import { graph } from '@@/db/graph'; import { graphql } from '@@/db/lib/utilities'; import { WEBCAL_STALE_TIME } from '@@/CONSTANTS'; -import { - Buckets, - deleteS3File, - getS3File, - listS3Files, - uploadS3File, -} from '@@/s3/s3'; +import { s3 } from '@@/s3'; +import { Buckets } from '@@/s3/s3'; const BUCKET = Buckets.RESOURCE_BUCKET; const PATH = 'ics/'; @@ -42,7 +37,7 @@ export async function getStaysMostRecent(after: number) { } export async function getCacheTimestamp() { - const files = await listS3Files(BUCKET, PATH); + const files = await s3.listFiles(BUCKET, PATH); if (!files?.data?.length) return null; const recent = files.data @@ -56,7 +51,7 @@ export async function getCacheTimestamp() { } export async function retrieveCache(ts: number) { - const data = await getS3File({ + const data = await s3.getFile({ bucket: BUCKET, path: PATH + `${ts}.ics`, }); @@ -73,7 +68,7 @@ export async function retrieveCache(ts: number) { export async function updateCache(createdTS: number, newFile: string) { const lastCache = await getCacheTimestamp(); - const success = await uploadS3File( + const success = await s3.uploadFile( { bucket: BUCKET, path: PATH + `${createdTS}.ics`, @@ -86,7 +81,7 @@ export async function updateCache(createdTS: number, newFile: string) { } if (lastCache) { - deleteS3File({ + s3.deleteFile({ bucket: BUCKET, path: PATH + `${lastCache}.ics`, }).catch(() => {}); diff --git a/server/src/s3/index.ts b/server/src/s3/index.ts new file mode 100644 index 0000000..68f7a91 --- /dev/null +++ b/server/src/s3/index.ts @@ -0,0 +1,3 @@ +export * from './s3'; +export * as default from './s3'; +export * as s3 from './s3'; diff --git a/server/src/s3/s3.ts b/server/src/s3/s3.ts index e8da0ce..47a89e9 100644 --- a/server/src/s3/s3.ts +++ b/server/src/s3/s3.ts @@ -35,7 +35,7 @@ type GetUploadUrlProps = { // type: expiresSec?: number; }; -export async function getS3UploadUrl({ +export async function getUploadUrl({ bucket, path, expiresSec, @@ -54,7 +54,7 @@ export async function getS3UploadUrl({ return { data: url }; } -export async function uploadS3File( +export async function uploadFile( fp: FilePath, options?: Partial ) { @@ -74,22 +74,19 @@ export async function uploadS3File( }); } -export async function getSignedS3Url( +export async function getSignedUrl( uri: string, expiresSec?: number ): Promise; -export async function getSignedS3Url( +export async function getSignedUrl( filePath: FilePath, expiresSec?: number ): Promise; -export async function getSignedS3Url( - p: string | FilePath, - expiresSec?: number -) { +export async function getSignedUrl(p: string | FilePath, expiresSec?: number) { let fp: FilePath; if (typeof p === 'string') { // parse S3 URI if needed - const parsed = parseS3Uri(p); + const parsed = parseUri(p); if (!parsed) return null; fp = parsed; } else fp = p; @@ -111,7 +108,7 @@ function presignFile(fp: FilePath, expiresSec?: number) { ); } -export function parseS3Uri(uri: string) { +export function parseUri(uri: string) { const m = uri?.match(/^s3:\/\/([^\/]+)\/(.+)$/); if (!m) return null; return { @@ -119,11 +116,11 @@ export function parseS3Uri(uri: string) { path: m[2], } as FilePath; } -export function getS3Uri(fp: FilePath) { +export function getUri(fp: FilePath) { return `s3://${fp.bucket}/${fp.path}`; } -export async function getS3File(fp: FilePath) { +export async function getFile(fp: FilePath) { try { const { Body } = await s3.send( new GetObjectCommand({ @@ -171,7 +168,7 @@ export async function doesFileExist(fp: FilePath) { * delete a file * @throws usually an {@link S3ServiceException} */ -export async function deleteS3File(fp: FilePath) { +export async function deleteFile(fp: FilePath) { await s3.send( new DeleteObjectCommand({ Bucket: fp.bucket, @@ -180,7 +177,7 @@ export async function deleteS3File(fp: FilePath) { ); } -export async function deleteS3Folder(fp: FilePath) { +export async function deleteFolder(fp: FilePath) { let deleted = 0; // number of files deleted const errors: { key: string; code: string }[] = []; @@ -223,7 +220,7 @@ export async function deleteS3Folder(fp: FilePath) { return { deleted, errors }; } -export async function listS3Files( +export async function listFiles( bucket: Buckets, folder?: string, paged?: { @@ -259,7 +256,7 @@ export async function listS3Files( * move or copy (or rename) a file * @throws usually an {@link S3ServiceException} */ -export async function moveS3File({ +export async function moveFile({ bucket, path, newPath, @@ -290,7 +287,7 @@ export async function moveS3File({ * create a new empty folder object. * @throws usually an {@link S3ServiceException} */ -export async function createS3Folder(bucket: Buckets, folder: string) { +export async function createFolder(bucket: Buckets, folder: string) { await s3.send( new PutObjectCommand({ Bucket: bucket, diff --git a/server/tsconfig.json b/server/tsconfig.json index fb9929a..ae7aec4 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -9,6 +9,7 @@ "esModuleInterop": true, "moduleResolution": "Bundler", "incremental": true, + "jsx": "react-jsx", "paths": { "@@/*": ["./src/*"], "graphql-modules": ["./node_modules/graphql-modules/index.d.ts"]