From 41fd8a509926a304a63d128759757c8c6a55fa4b Mon Sep 17 00:00:00 2001 From: Unchained Date: Fri, 27 Mar 2026 07:16:03 +0200 Subject: [PATCH] feat: add settings UI for webhook toggles - Add settings store (src/lib/settings.ts) for persistent configuration - Add settings API route for CRUD operations - Add settings page with webhook toggles for order events - Update webhook handlers to check settings before sending emails - Settings include: webhook enable/disable, admin/customer notification toggles - Email configuration: from name, from email, admin emails, store/dashboard URLs --- pnpm-lock.yaml | 726 +++++++++++++++++++++- src/lib/settings.ts | 133 ++++ src/pages/api/settings.ts | 34 + src/pages/api/webhooks/order-cancelled.ts | 51 +- src/pages/api/webhooks/order-created.ts | 73 ++- src/pages/api/webhooks/order-fulfilled.ts | 51 +- src/pages/settings.tsx | 410 ++++++++++++ 7 files changed, 1403 insertions(+), 75 deletions(-) create mode 100644 src/lib/settings.ts create mode 100644 src/pages/api/settings.ts create mode 100644 src/pages/settings.tsx diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 16b0268..483e8cb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,12 @@ importers: .: dependencies: + '@react-email/components': + specifier: ^0.0.19 + version: 0.0.19(@types/react@18.2.25)(react@18.3.1) '@saleor/app-sdk': specifier: 1.5.0 - version: 1.5.0(graphql@16.8.1)(next@15.5.9(@babel/core@7.23.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 1.5.0(graphql@16.8.1)(next@15.5.9(@babel/core@7.24.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@saleor/macaw-ui': specifier: 1.4.1 version: 1.4.1(@types/react-dom@18.2.10)(@types/react@18.2.25)(@vanilla-extract/css@1.14.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -19,13 +22,16 @@ importers: version: 1.0.0(graphql@16.8.1) next: specifier: 15.5.9 - version: 15.5.9(@babel/core@7.23.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 15.5.9(@babel/core@7.24.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: 18.3.1 version: 18.3.1 react-dom: specifier: 18.3.1 version: 18.3.1(react@18.3.1) + resend: + specifier: ^6.9.4 + version: 6.9.4(@react-email/render@0.0.15) urql: specifier: ^4.0.2 version: 4.0.5(graphql@16.8.1)(react@18.3.1) @@ -35,7 +41,7 @@ importers: version: 3.2.0(graphql@16.8.1) '@graphql-codegen/cli': specifier: 3.3.1 - version: 3.3.1(@babel/core@7.23.0)(@types/node@18.18.3)(enquirer@2.4.1)(graphql@16.8.1) + version: 3.3.1(@babel/core@7.24.0)(@types/node@18.18.3)(enquirer@2.4.1)(graphql@16.8.1) '@graphql-codegen/introspection': specifier: 3.0.1 version: 3.0.1(graphql@16.8.1) @@ -1132,89 +1138,105 @@ packages: resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.2.4': resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-ppc64@1.2.4': resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-riscv64@1.2.4': resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.2.4': resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.2.4': resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.2.4': resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.2.4': resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.34.5': resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.34.5': resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-ppc64@0.34.5': resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [ppc64] os: [linux] + libc: [glibc] '@img/sharp-linux-riscv64@0.34.5': resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [riscv64] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.34.5': resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.34.5': resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.34.5': resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.34.5': resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.34.5': resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} @@ -1239,6 +1261,10 @@ packages: cpu: [x64] os: [win32] + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + '@jest/schemas@29.6.3': resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -1293,24 +1319,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@next/swc-linux-arm64-musl@15.5.7': resolution: {integrity: sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@next/swc-linux-x64-gnu@15.5.7': resolution: {integrity: sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@next/swc-linux-x64-musl@15.5.7': resolution: {integrity: sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@next/swc-win32-arm64-msvc@15.5.7': resolution: {integrity: sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==} @@ -1336,6 +1366,9 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@one-ini/wasm@0.1.1': + resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + '@opentelemetry/api@1.9.0': resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} @@ -1373,30 +1406,35 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-glibc@2.3.0': resolution: {integrity: sha512-mQ0gBSQEiq1k/MMkgcSB0Ic47UORZBmWoAWlMrTW6nbAGoLZP+h7AtUM7H3oDu34TBFFvjy4JCGP43JlylkTQA==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.3.0': resolution: {integrity: sha512-LXZAExpepJew0Gp8ZkJ+xDZaTQjLHv48h0p0Vw2VMFQ8A+RKrAvpFuPVCVwKJCr5SE+zvaG+Etg56qXvTDIedw==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.3.0': resolution: {integrity: sha512-P7Wo91lKSeSgMTtG7CnBS6WrA5otr1K7shhSjKHNePVmfBHDoAOHYRXgUmhiNfbcGk0uMCHVcdbfxtuiZCHVow==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.3.0': resolution: {integrity: sha512-+kiRE1JIq8QdxzwoYY+wzBs9YbJ34guBweTK8nlzLKimn5EQ2b2FSC+tAOpq302BuIMjyuUGvBiUhEcLIGMQ5g==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-win32-arm64@2.3.0': resolution: {integrity: sha512-35gXCnaz1AqIXpG42evcoP2+sNL62gZTMZne3IackM+6QlfMcJLy3DrjuL6Iks7Czpd3j4xRBzez3ADCj1l7Aw==} @@ -1431,6 +1469,10 @@ packages: resolution: {integrity: sha512-VtaY4spKTdN5LjJ04im/d/joXuvLbQdgy5Z4DXF4MFZhQ+MTrejbNMkfZBp1Bs3O5+bFqnJgyGdPuZQflvIa5A==} engines: {node: '>=10.12.0'} + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + '@radix-ui/primitive@1.0.1': resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==} @@ -1814,6 +1856,128 @@ packages: '@radix-ui/rect@1.0.1': resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==} + '@react-email/body@0.0.8': + resolution: {integrity: sha512-gqdkNYlIaIw0OdpWu8KjIcQSIFvx7t2bZpXVxMMvBS859Ia1+1X3b5RNbjI3S1ZqLddUf7owOHkO4MiXGE+nxg==} + peerDependencies: + react: ^18.2.0 + + '@react-email/button@0.0.15': + resolution: {integrity: sha512-9Zi6SO3E8PoHYDfcJTecImiHLyitYWmIRs0HE3Ogra60ZzlWP2EXu+AZqwQnhXuq+9pbgwBWNWxB5YPetNPTNA==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.2.0 + + '@react-email/code-block@0.0.4': + resolution: {integrity: sha512-xjVLi/9dFNJ70N7hYme+21eQWa3b9/kgp4V+FKQJkQCuIMobxPRCIGM5jKD/0Vo2OqrE5chYv/dkg/aP8a8sPg==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.2.0 + + '@react-email/code-inline@0.0.2': + resolution: {integrity: sha512-0cmgbbibFeOJl0q04K9jJlPDuJ+SEiX/OG6m3Ko7UOkG3TqjRD8Dtvkij6jNDVfUh/zESpqJCP2CxrCLLMUjdA==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.2.0 + + '@react-email/column@0.0.10': + resolution: {integrity: sha512-MnP8Mnwipr0X3XtdD6jMLckb0sI5/IlS6Kl/2F6/rsSWBJy5Gg6nizlekTdkwDmy0kNSe3/1nGU0Zqo98pl63Q==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.2.0 + + '@react-email/components@0.0.19': + resolution: {integrity: sha512-yf49eIq0NDDXzO2RTZaT8fKa16eKUFMdWWMx4V5Bq+b2JdGuAMobO5s9Ea6azSVL6RDcJ8epdY1TCR2kL2PPHw==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.2.0 + + '@react-email/container@0.0.12': + resolution: {integrity: sha512-HFu8Pu5COPFfeZxSL+wKv/TV5uO/sp4zQ0XkRCdnGkj/xoq0lqOHVDL4yC2Pu6fxXF/9C3PHDA++5uEYV5WVJw==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.2.0 + + '@react-email/font@0.0.6': + resolution: {integrity: sha512-sZZFvEZ4U3vNCAZ8wXqIO3DuGJR2qE/8m2fEH+tdqwa532zGO3zW+UlCTg0b9455wkJSzEBeaWik0IkNvjXzxw==} + peerDependencies: + react: ^18.2.0 + + '@react-email/head@0.0.9': + resolution: {integrity: sha512-dF3Uv1qy3oh+IU2atXdv5Xk0hk2udOlMb1A/MNGngC0eHyoEV9ThA0XvhN7mm5x9dDLkVamoWUKXDtmkiuSRqQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.2.0 + + '@react-email/heading@0.0.12': + resolution: {integrity: sha512-eB7mpnAvDmwvQLoPuwEiPRH4fPXWe6ltz6Ptbry2BlI88F0a2k11Ghb4+sZHBqg7vVw/MKbqEgtLqr3QJ/KfCQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.2.0 + + '@react-email/hr@0.0.8': + resolution: {integrity: sha512-JLVvpCg2wYKEB+n/PGCggWG9fRU5e4lxsGdpK5SDLsCL0ic3OLKSpHMfeE+ZSuw0GixAVVQN7F64PVJHQkd4MQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.2.0 + + '@react-email/html@0.0.8': + resolution: {integrity: sha512-arII3wBNLpeJtwyIJXPaILm5BPKhA+nvdC1F9QkuKcOBJv2zXctn8XzPqyGqDfdplV692ulNJP7XY55YqbKp6w==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.2.0 + + '@react-email/img@0.0.8': + resolution: {integrity: sha512-jx/rPuKo31tV18fu7P5rRqelaH5wkhg83Dq7uLwJpfqhbi4KFBGeBfD0Y3PiLPPoh+WvYf+Adv9W2ghNW8nOMQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.2.0 + + '@react-email/link@0.0.8': + resolution: {integrity: sha512-nVikuTi8WJHa6Baad4VuRUbUCa/7EtZ1Qy73TRejaCHn+vhetc39XGqHzKLNh+Z/JFL8Hv9g+4AgG16o2R0ogQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.2.0 + + '@react-email/markdown@0.0.10': + resolution: {integrity: sha512-MH0xO+NJ4IuJcx9nyxbgGKAMXyudFjCZ0A2GQvuWajemW9qy2hgnJ3mW3/z5lwcenG+JPn7JyO/iZpizQ7u1tA==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.2.0 + + '@react-email/preview@0.0.9': + resolution: {integrity: sha512-2fyAA/zzZYfYmxfyn3p2YOIU30klyA6Dq4ytyWq4nfzQWWglt5hNDE0cMhObvRtfjM9ghMSVtoELAb0MWiF/kw==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.2.0 + + '@react-email/render@0.0.15': + resolution: {integrity: sha512-/pT5dBu0y1mogrfEpc002rgRcXpbShK6PFtxTVU6LZZ+bccvZPgk67HKc01lxpa1eYGQgZ6I+VQ02GRnMDclTg==} + engines: {node: '>=18.0.0'} + + '@react-email/row@0.0.8': + resolution: {integrity: sha512-JsB6pxs/ZyjYpEML3nbwJRGAerjcN/Pa/QG48XUwnT/MioDWrUuyQuefw+CwCrSUZ2P1IDrv2tUD3/E3xzcoKw==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.2.0 + + '@react-email/section@0.0.12': + resolution: {integrity: sha512-UCD/N/BeOTN4h3VZBUaFdiSem6HnpuxD1Q51TdBFnqeNqS5hBomp8LWJJ9s4gzwHWk1XPdNfLA3I/fJwulJshg==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.2.0 + + '@react-email/tailwind@0.0.18': + resolution: {integrity: sha512-ob8CXX/Pqq1U8YfL5OJTL48WJkixizyoXMMRYTiDLDN9LVLU7lSLtcK9kOD9CgFbO2yUPQr7/5+7gnQJ+cXa8Q==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.2.0 + + '@react-email/text@0.0.8': + resolution: {integrity: sha512-uvN2TNWMrfC9wv/LLmMLbbEN1GrMWZb9dBK14eYxHHAEHCeyvGb5ePZZ2MPyzO7Y5yTC+vFEnCEr76V+hWMxCQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.2.0 + '@repeaterjs/repeater@3.0.4': resolution: {integrity: sha512-AW8PKd6iX3vAZ0vA43nOUOnbq/X5ihgU+mSXXqunMkeQADGiqw/PY0JNeYtD5sr0PAy51YPgAPbDoeapv9r8WA==} @@ -1841,46 +2005,55 @@ packages: resolution: {integrity: sha512-ADm/xt86JUnmAfA9mBqFcRp//RVRt1ohGOYF6yL+IFCYqOBNwy5lbEK05xTsEoJq+/tJzg8ICUtS82WinJRuIw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.16.4': resolution: {integrity: sha512-tJfJaXPiFAG+Jn3cutp7mCs1ePltuAgRqdDZrzb1aeE3TktWWJ+g7xK9SNlaSUFw6IU4QgOxAY4rA+wZUT5Wfg==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.16.4': resolution: {integrity: sha512-7dy1BzQkgYlUTapDTvK997cgi0Orh5Iu7JlZVBy1MBURk7/HSbHkzRnXZa19ozy+wwD8/SlpJnOOckuNZtJR9w==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.16.4': resolution: {integrity: sha512-zsFwdUw5XLD1gQe0aoU2HVceI6NEW7q7m05wA46eUAyrkeNYExObfRFQcvA6zw8lfRc5BHtan3tBpo+kqEOxmg==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-powerpc64le-gnu@4.16.4': resolution: {integrity: sha512-p8C3NnxXooRdNrdv6dBmRTddEapfESEUflpICDNKXpHvTjRRq1J82CbU5G3XfebIZyI3B0s074JHMWD36qOW6w==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.16.4': resolution: {integrity: sha512-Lh/8ckoar4s4Id2foY7jNgitTOUQczwMWNYi+Mjt0eQ9LKhr6sK477REqQkmy8YHY3Ca3A2JJVdXnfb3Rrwkng==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-s390x-gnu@4.16.4': resolution: {integrity: sha512-1xwwn9ZCQYuqGmulGsTZoKrrn0z2fAur2ujE60QgyDpHmBbXbxLaQiEvzJWDrscRq43c8DnuHx3QorhMTZgisQ==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.16.4': resolution: {integrity: sha512-LuOGGKAJ7dfRtxVnO1i3qWc6N9sh0Em/8aZ3CezixSTM+E9Oq3OvTsvC4sm6wWjzpsIlOCnZjdluINKESflJLA==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.16.4': resolution: {integrity: sha512-ch86i7KkJKkLybDP2AtySFTRi5fM3KXp0PnHocHuJMdZwu7BuyIKi35BE9guMlmTpwwBTB3ljHj9IQXnTCD0vA==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.16.4': resolution: {integrity: sha512-Ma4PwyLfOWZWayfEsNQzTDBVW8PZ6TUUN1uFTBQbF2Chv/+sjenE86lpiEwj2FiviSmSZ4Ap4MaAfl1ciF4aSA==} @@ -1952,9 +2125,15 @@ packages: lucide-react: optional: true + '@selderee/plugin-htmlparser2@0.11.0': + resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} + '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + '@stablelib/base64@1.0.1': + resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==} + '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} @@ -2117,6 +2296,10 @@ packages: resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} deprecated: Use your platform's native atob() and btoa() methods instead + abbrev@2.0.0: + resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + acorn-globals@7.0.1: resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} @@ -2170,6 +2353,10 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} @@ -2182,6 +2369,10 @@ packages: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -2287,6 +2478,9 @@ packages: brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -2424,6 +2618,10 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + common-tags@1.8.2: resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} engines: {node: '>=4.0.0'} @@ -2437,6 +2635,9 @@ packages: confbox@0.1.7: resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==} + 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==} @@ -2461,6 +2662,10 @@ packages: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + css-what@6.1.0: resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} engines: {node: '>= 6'} @@ -2605,11 +2810,24 @@ 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==} + domexception@4.0.0: resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==} engines: {node: '>=12'} deprecated: Use your platform's native DOMException instead + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + dot-case@3.0.4: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} @@ -2626,6 +2844,14 @@ packages: resolution: {integrity: sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q==} engines: {node: '>=4'} + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + editorconfig@1.0.7: + resolution: {integrity: sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw==} + engines: {node: '>=14'} + hasBin: true + electron-to-chromium@1.4.542: resolution: {integrity: sha512-6+cpa00G09N3sfh2joln4VUXHquWrOFx3FLZqiVQvl45+zS9DskDBTPvob+BhvFRmTBkyDSk0vvLMMRo/qc6mQ==} @@ -2855,6 +3081,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==} @@ -2871,6 +3100,9 @@ packages: fast-querystring@1.1.2: resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} + fast-sha256@1.3.0: + resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==} + fast-url-parser@1.1.3: resolution: {integrity: sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==} @@ -2924,6 +3156,10 @@ packages: for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + form-data@4.0.0: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} @@ -2994,6 +3230,11 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + glob@7.1.7: resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==} deprecated: Glob versions prior to v9 are no longer supported @@ -3098,6 +3339,13 @@ packages: resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} engines: {node: '>=12'} + html-to-text@9.0.5: + resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==} + engines: {node: '>=14'} + + htmlparser2@8.0.2: + resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} @@ -3167,6 +3415,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'} @@ -3330,6 +3581,9 @@ packages: iterator.prototype@1.1.2: resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jiti@1.17.1: resolution: {integrity: sha512-NZIITw8uZQFuzQimqjUxIrIcEdxYDFIe/0xYfIlVXTkiBjjyBEvgasj5bb0/cHtPRD/NziPbT312sFrkI5ALpw==} hasBin: true @@ -3344,6 +3598,15 @@ packages: jose@5.10.0: resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==} + js-beautify@1.15.4: + resolution: {integrity: sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==} + engines: {node: '>=14'} + hasBin: true + + js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + js-sdsl@4.4.2: resolution: {integrity: sha512-dwXFwByc/ajSV6m5bcKAPwe4yDDF6D614pxmIi5odytzxRlwqF6nwoiCek80Ixc7Cvma5awClxrzFtxCQvcM8w==} @@ -3427,6 +3690,9 @@ packages: language-tags@1.0.5: resolution: {integrity: sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==} + 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'} @@ -3485,6 +3751,9 @@ packages: lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@4.1.5: resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} @@ -3502,6 +3771,16 @@ packages: resolution: {integrity: sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==} engines: {node: '>=0.10.0'} + 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 + media-query-parser@2.0.2: resolution: {integrity: sha512-1N4qp+jE0pL5Xv4uEcwVUhIkwdUO3S/9gML90nqKA7v7FcOS5vUtatfzok9S9U1EJU8dHWlcv95WLnKmmxZI9w==} @@ -3548,9 +3827,17 @@ packages: resolution: {integrity: sha512-lIUdtK5hdofgCTu3aT0sOaHsYR37viUuIc0rwnnDXImbwFRcumyLMeZaM0t0I/fgxS6s6JMfu0rLD1Wz9pv1ng==} engines: {node: '>=10'} + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + mlly@1.6.1: resolution: {integrity: sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==} @@ -3623,6 +3910,11 @@ packages: node-releases@2.0.14: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} + 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'} @@ -3735,6 +4027,9 @@ packages: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + package-manager-detector@0.2.0: resolution: {integrity: sha512-E385OSk9qDcXhcM9LNSe4sdhx8a9mAPrZ4sMLW+tmxl5ZuGtPUcdFu+MPP2jbgiWAZ6Pfe5soGFMd+0Db5Vrog==} @@ -3756,6 +4051,9 @@ packages: parse5@7.1.2: resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + parseley@0.12.1: + resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==} + pascal-case@3.1.2: resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} @@ -3789,6 +4087,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-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -3799,6 +4101,9 @@ packages: pathval@1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + peberminta@0.9.0: + resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} + picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} @@ -3820,6 +4125,9 @@ packages: pkg-types@1.1.0: resolution: {integrity: sha512-/RpmvKdxKf8uILTtoOhAgf30wYbP2Qw+L9p3Rvshx1JZVX+XQNZQFjlbmGHEGIm4CkVPlSn+NXmIM8+9oWQaSA==} + postal-mime@2.7.3: + resolution: {integrity: sha512-MjhXadAJaWgYzevi46+3kLak8y6gbg0ku14O1gO/LNOuay8dO+1PtcSGvAdgDR0DoIsSaiIA8y/Ddw6MnrO0Tw==} + postcss@8.4.31: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} @@ -3846,12 +4154,19 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + prismjs@1.29.0: + resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} + engines: {node: '>=6'} + promise@7.3.1: resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==} prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + pseudomap@1.0.2: resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} @@ -3896,6 +4211,9 @@ packages: react-is@18.3.0: resolution: {integrity: sha512-wRiUsea88TjKDc4FBEn+sLvIDesp6brMbGWnJGjew2waAc9evdhja/2LvePc898HJbHw0L+MTWy7NhpnELAvLQ==} + react-promise-suspense@0.3.4: + resolution: {integrity: sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==} + react-refresh@0.14.0: resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==} engines: {node: '>=0.10.0'} @@ -3982,6 +4300,15 @@ packages: requires-port@1.0.0: resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + resend@6.9.4: + resolution: {integrity: sha512-/M3dsJzu5OgozqVsA4Psd/1L7EdePgOIIxClas453GOQYFG3VHc2ZyCHZFlvqsc9aZCCd2BJRRqZgWC8D9c7/g==} + engines: {node: '>=20'} + peerDependencies: + '@react-email/render': '*' + peerDependenciesMeta: + '@react-email/render': + optional: true + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -4055,6 +4382,9 @@ packages: scuid@1.1.0: resolution: {integrity: sha512-MuCAyrGZcTLfQoH2XoBlQ8C6bzwN88XT/0slOGz0pn8+gIP85BOAfYa44ZXQUTOwRwPU0QvgU+V+OSajl/59Xg==} + selderee@0.11.0: + resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -4159,6 +4489,9 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + standardwebhooks@1.0.0: + resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==} + statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} @@ -4177,6 +4510,10 @@ packages: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + string.prototype.matchall@4.0.10: resolution: {integrity: sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==} @@ -4197,6 +4534,10 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} @@ -4237,6 +4578,9 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + svix@1.86.0: + resolution: {integrity: sha512-/HTvXwjLJe1l/MsLXAO1ddCYxElJk4eNR4DzOjDOEmGrPN/3BtBE8perGwMAaJ2sT5T172VkBYzmHcjUfM1JRQ==} + swap-case@2.0.2: resolution: {integrity: sha512-kc6S2YS/2yXbtkSMunBtKdah4VFETZ8Oh6ONSmSd9bRxhqTrtARUCBUiWXH3xVPpvR7tz2CSnkuXVE42EcGnMw==} @@ -4463,6 +4807,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} + hasBin: true + value-or-promise@1.0.12: resolution: {integrity: sha512-Z6Uz+TYwEqE7ZN50gwn+1LCVo9ZVrpxRPOhOLnncYkY1ZzOYtrX8Fwf/rFktZ8R5mJms6EZf5TqNOMeZmnPq9Q==} engines: {node: '>=12'} @@ -4611,6 +4959,10 @@ packages: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -4716,7 +5068,7 @@ snapshots: '@babel/core': 7.23.0 '@babel/generator': 7.23.0 '@babel/parser': 7.23.0 - '@babel/runtime': 7.23.1 + '@babel/runtime': 7.25.6 '@babel/traverse': 7.23.0 '@babel/types': 7.23.0 babel-preset-fbjs: 3.4.0(@babel/core@7.23.0) @@ -4975,9 +5327,9 @@ snapshots: '@babel/core': 7.23.0 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-import-assertions@7.22.5(@babel/core@7.23.0)': + '@babel/plugin-syntax-import-assertions@7.22.5(@babel/core@7.24.0)': dependencies: - '@babel/core': 7.23.0 + '@babel/core': 7.24.0 '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-jsx@7.22.5(@babel/core@7.23.0)': @@ -5526,7 +5878,7 @@ snapshots: graphql: 16.8.1 tslib: 2.4.1 - '@graphql-codegen/cli@3.3.1(@babel/core@7.23.0)(@types/node@18.18.3)(enquirer@2.4.1)(graphql@16.8.1)': + '@graphql-codegen/cli@3.3.1(@babel/core@7.24.0)(@types/node@18.18.3)(enquirer@2.4.1)(graphql@16.8.1)': dependencies: '@babel/generator': 7.23.0 '@babel/template': 7.22.15 @@ -5534,9 +5886,9 @@ snapshots: '@graphql-codegen/core': 3.1.0(graphql@16.8.1) '@graphql-codegen/plugin-helpers': 4.2.0(graphql@16.8.1) '@graphql-tools/apollo-engine-loader': 7.3.26(graphql@16.8.1) - '@graphql-tools/code-file-loader': 7.3.23(@babel/core@7.23.0)(graphql@16.8.1) - '@graphql-tools/git-loader': 7.3.0(@babel/core@7.23.0)(graphql@16.8.1) - '@graphql-tools/github-loader': 7.3.28(@babel/core@7.23.0)(@types/node@18.18.3)(graphql@16.8.1) + '@graphql-tools/code-file-loader': 7.3.23(@babel/core@7.24.0)(graphql@16.8.1) + '@graphql-tools/git-loader': 7.3.0(@babel/core@7.24.0)(graphql@16.8.1) + '@graphql-tools/github-loader': 7.3.28(@babel/core@7.24.0)(@types/node@18.18.3)(graphql@16.8.1) '@graphql-tools/graphql-file-loader': 7.5.17(graphql@16.8.1) '@graphql-tools/json-file-loader': 7.4.18(graphql@16.8.1) '@graphql-tools/load': 7.8.14(graphql@16.8.1) @@ -5726,9 +6078,9 @@ snapshots: tslib: 2.6.2 value-or-promise: 1.0.12 - '@graphql-tools/code-file-loader@7.3.23(@babel/core@7.23.0)(graphql@16.8.1)': + '@graphql-tools/code-file-loader@7.3.23(@babel/core@7.24.0)(graphql@16.8.1)': dependencies: - '@graphql-tools/graphql-tag-pluck': 7.5.2(@babel/core@7.23.0)(graphql@16.8.1) + '@graphql-tools/graphql-tag-pluck': 7.5.2(@babel/core@7.24.0)(graphql@16.8.1) '@graphql-tools/utils': 9.2.1(graphql@16.8.1) globby: 11.1.0 graphql: 16.8.1 @@ -5798,9 +6150,9 @@ snapshots: tslib: 2.6.2 value-or-promise: 1.0.12 - '@graphql-tools/git-loader@7.3.0(@babel/core@7.23.0)(graphql@16.8.1)': + '@graphql-tools/git-loader@7.3.0(@babel/core@7.24.0)(graphql@16.8.1)': dependencies: - '@graphql-tools/graphql-tag-pluck': 7.5.2(@babel/core@7.23.0)(graphql@16.8.1) + '@graphql-tools/graphql-tag-pluck': 7.5.2(@babel/core@7.24.0)(graphql@16.8.1) '@graphql-tools/utils': 9.2.1(graphql@16.8.1) graphql: 16.8.1 is-glob: 4.0.3 @@ -5811,11 +6163,11 @@ snapshots: - '@babel/core' - supports-color - '@graphql-tools/github-loader@7.3.28(@babel/core@7.23.0)(@types/node@18.18.3)(graphql@16.8.1)': + '@graphql-tools/github-loader@7.3.28(@babel/core@7.24.0)(@types/node@18.18.3)(graphql@16.8.1)': dependencies: '@ardatan/sync-fetch': 0.0.1 '@graphql-tools/executor-http': 0.1.10(@types/node@18.18.3)(graphql@16.8.1) - '@graphql-tools/graphql-tag-pluck': 7.5.2(@babel/core@7.23.0)(graphql@16.8.1) + '@graphql-tools/graphql-tag-pluck': 7.5.2(@babel/core@7.24.0)(graphql@16.8.1) '@graphql-tools/utils': 9.2.1(graphql@16.8.1) '@whatwg-node/fetch': 0.8.8 graphql: 16.8.1 @@ -5836,10 +6188,10 @@ snapshots: tslib: 2.6.2 unixify: 1.0.0 - '@graphql-tools/graphql-tag-pluck@7.5.2(@babel/core@7.23.0)(graphql@16.8.1)': + '@graphql-tools/graphql-tag-pluck@7.5.2(@babel/core@7.24.0)(graphql@16.8.1)': dependencies: '@babel/parser': 7.23.0 - '@babel/plugin-syntax-import-assertions': 7.22.5(@babel/core@7.23.0) + '@babel/plugin-syntax-import-assertions': 7.22.5(@babel/core@7.24.0) '@babel/traverse': 7.23.0 '@babel/types': 7.23.0 '@graphql-tools/utils': 9.2.1(graphql@16.8.1) @@ -6084,6 +6436,15 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + '@jest/schemas@29.6.3': dependencies: '@sinclair/typebox': 0.27.8 @@ -6165,6 +6526,8 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.15.0 + '@one-ini/wasm@0.1.1': {} + '@opentelemetry/api@1.9.0': {} '@opentelemetry/semantic-conventions@1.30.0': {} @@ -6243,6 +6606,9 @@ snapshots: tslib: 2.6.2 webcrypto-core: 1.7.7 + '@pkgjs/parseargs@0.11.0': + optional: true + '@radix-ui/primitive@1.0.1': dependencies: '@babel/runtime': 7.25.6 @@ -6669,6 +7035,121 @@ snapshots: dependencies: '@babel/runtime': 7.25.6 + '@react-email/body@0.0.8(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/button@0.0.15(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/code-block@0.0.4(react@18.3.1)': + dependencies: + prismjs: 1.29.0 + react: 18.3.1 + + '@react-email/code-inline@0.0.2(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/column@0.0.10(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/components@0.0.19(@types/react@18.2.25)(react@18.3.1)': + dependencies: + '@react-email/body': 0.0.8(react@18.3.1) + '@react-email/button': 0.0.15(react@18.3.1) + '@react-email/code-block': 0.0.4(react@18.3.1) + '@react-email/code-inline': 0.0.2(react@18.3.1) + '@react-email/column': 0.0.10(react@18.3.1) + '@react-email/container': 0.0.12(react@18.3.1) + '@react-email/font': 0.0.6(react@18.3.1) + '@react-email/head': 0.0.9(react@18.3.1) + '@react-email/heading': 0.0.12(@types/react@18.2.25)(react@18.3.1) + '@react-email/hr': 0.0.8(react@18.3.1) + '@react-email/html': 0.0.8(react@18.3.1) + '@react-email/img': 0.0.8(react@18.3.1) + '@react-email/link': 0.0.8(react@18.3.1) + '@react-email/markdown': 0.0.10(react@18.3.1) + '@react-email/preview': 0.0.9(react@18.3.1) + '@react-email/render': 0.0.15 + '@react-email/row': 0.0.8(react@18.3.1) + '@react-email/section': 0.0.12(react@18.3.1) + '@react-email/tailwind': 0.0.18(react@18.3.1) + '@react-email/text': 0.0.8(react@18.3.1) + react: 18.3.1 + transitivePeerDependencies: + - '@types/react' + + '@react-email/container@0.0.12(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/font@0.0.6(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/head@0.0.9(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/heading@0.0.12(@types/react@18.2.25)(react@18.3.1)': + dependencies: + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.25)(react@18.3.1) + react: 18.3.1 + transitivePeerDependencies: + - '@types/react' + + '@react-email/hr@0.0.8(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/html@0.0.8(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/img@0.0.8(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/link@0.0.8(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/markdown@0.0.10(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.9(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/render@0.0.15': + dependencies: + html-to-text: 9.0.5 + js-beautify: 1.15.4 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-promise-suspense: 0.3.4 + + '@react-email/row@0.0.8(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/section@0.0.12(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/tailwind@0.0.18(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@react-email/text@0.0.8(react@18.3.1)': + dependencies: + react: 18.3.1 + '@repeaterjs/repeater@3.0.4': {} '@rollup/rollup-android-arm-eabi@4.16.4': @@ -6721,7 +7202,7 @@ snapshots: '@rushstack/eslint-patch@1.5.1': {} - '@saleor/app-sdk@1.5.0(graphql@16.8.1)(next@15.5.9(@babel/core@7.23.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@saleor/app-sdk@1.5.0(graphql@16.8.1)(next@15.5.9(@babel/core@7.24.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.30.0 @@ -6730,7 +7211,7 @@ snapshots: raw-body: 3.0.0 optionalDependencies: graphql: 16.8.1 - next: 15.5.9(@babel/core@7.23.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 15.5.9(@babel/core@7.24.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) transitivePeerDependencies: @@ -6769,8 +7250,15 @@ snapshots: transitivePeerDependencies: - '@vanilla-extract/css' + '@selderee/plugin-htmlparser2@0.11.0': + dependencies: + domhandler: 5.0.3 + selderee: 0.11.0 + '@sinclair/typebox@0.27.8': {} + '@stablelib/base64@1.0.1': {} + '@swc/helpers@0.5.15': dependencies: tslib: 2.8.1 @@ -7002,6 +7490,8 @@ snapshots: abab@2.0.6: {} + abbrev@2.0.0: {} + acorn-globals@7.0.1: dependencies: acorn: 8.10.0 @@ -7051,6 +7541,8 @@ snapshots: ansi-regex@5.0.1: {} + ansi-regex@6.2.2: {} + ansi-styles@3.2.1: dependencies: color-convert: 1.9.3 @@ -7061,6 +7553,8 @@ snapshots: ansi-styles@5.2.0: {} + ansi-styles@6.2.3: {} + argparse@1.0.10: dependencies: sprintf-js: 1.0.3 @@ -7212,6 +7706,10 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -7388,6 +7886,8 @@ snapshots: dependencies: delayed-stream: 1.0.0 + commander@10.0.1: {} + common-tags@1.8.2: {} compute-scroll-into-view@3.1.0: {} @@ -7396,6 +7896,11 @@ snapshots: confbox@0.1.7: {} + 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 @@ -7437,6 +7942,12 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + css-what@6.1.0: {} cssesc@3.0.0: {} @@ -7538,10 +8049,28 @@ 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: {} + domexception@4.0.0: dependencies: webidl-conversions: 7.0.0 + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + 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 @@ -7560,6 +8089,15 @@ snapshots: dset@3.1.2: {} + eastasianwidth@0.2.0: {} + + editorconfig@1.0.7: + dependencies: + '@one-ini/wasm': 0.1.1 + commander: 10.0.1 + minimatch: 9.0.9 + semver: 7.7.3 + electron-to-chromium@1.4.542: {} electron-to-chromium@1.4.692: {} @@ -7973,6 +8511,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.1: @@ -7991,6 +8531,8 @@ snapshots: dependencies: fast-decode-uri-component: 1.0.1 + fast-sha256@1.3.0: {} + fast-url-parser@1.1.3: dependencies: punycode: 1.4.1 @@ -8055,6 +8597,11 @@ snapshots: dependencies: is-callable: 1.2.7 + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + form-data@4.0.0: dependencies: asynckit: 0.4.0 @@ -8127,6 +8674,15 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + glob@7.1.7: dependencies: fs.realpath: 1.0.0 @@ -8242,6 +8798,21 @@ snapshots: dependencies: whatwg-encoding: 2.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 + + htmlparser2@8.0.2: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 4.5.0 + http-errors@2.0.0: dependencies: depd: 2.0.0 @@ -8315,6 +8886,8 @@ snapshots: inherits@2.0.4: {} + ini@1.3.8: {} + inquirer@8.2.6: dependencies: ansi-escapes: 4.3.2 @@ -8491,6 +9064,12 @@ snapshots: reflect.getprototypeof: 1.0.4 set-function-name: 2.0.1 + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + jiti@1.17.1: {} jiti@1.20.0: {} @@ -8499,6 +9078,16 @@ snapshots: jose@5.10.0: {} + js-beautify@1.15.4: + dependencies: + config-chain: 1.1.13 + editorconfig: 1.0.7 + glob: 10.5.0 + js-cookie: 3.0.5 + nopt: 7.2.1 + + js-cookie@3.0.5: {} + js-sdsl@4.4.2: {} js-tokens@4.0.0: {} @@ -8607,6 +9196,8 @@ snapshots: dependencies: language-subtag-registry: 0.3.22 + leac@0.6.0: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -8674,6 +9265,8 @@ snapshots: dependencies: tslib: 2.5.3 + lru-cache@10.4.3: {} + lru-cache@4.1.5: dependencies: pseudomap: 1.0.2 @@ -8693,6 +9286,13 @@ snapshots: map-cache@0.2.2: {} + marked@7.0.4: {} + + md-to-react-email@5.0.2(react@18.3.1): + dependencies: + marked: 7.0.4 + react: 18.3.1 + media-query-parser@2.0.2: dependencies: '@babel/runtime': 7.25.6 @@ -8728,8 +9328,14 @@ snapshots: dependencies: brace-expansion: 1.1.11 + minimatch@9.0.9: + dependencies: + brace-expansion: 2.0.2 + minimist@1.2.8: {} + minipass@7.1.3: {} + mlly@1.6.1: dependencies: acorn: 8.11.3 @@ -8751,7 +9357,7 @@ snapshots: natural-compare@1.4.0: {} - next@15.5.9(@babel/core@7.23.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@15.5.9(@babel/core@7.24.0)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@next/env': 15.5.9 '@swc/helpers': 0.5.15 @@ -8759,7 +9365,7 @@ snapshots: postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.6(@babel/core@7.23.0)(react@18.3.1) + styled-jsx: 5.1.6(@babel/core@7.24.0)(react@18.3.1) optionalDependencies: '@next/swc-darwin-arm64': 15.5.7 '@next/swc-darwin-x64': 15.5.7 @@ -8792,6 +9398,10 @@ snapshots: node-releases@2.0.14: {} + nopt@7.2.1: + dependencies: + abbrev: 2.0.0 + normalize-path@2.1.1: dependencies: remove-trailing-separator: 1.1.0 @@ -8918,6 +9528,8 @@ snapshots: p-try@2.2.0: {} + package-json-from-dist@1.0.1: {} + package-manager-detector@0.2.0: {} param-case@3.0.4: @@ -8946,6 +9558,11 @@ snapshots: dependencies: entities: 4.5.0 + parseley@0.12.1: + dependencies: + leac: 0.6.0 + peberminta: 0.9.0 + pascal-case@3.1.2: dependencies: no-case: 3.0.4 @@ -8972,12 +9589,19 @@ snapshots: dependencies: path-root-regex: 0.1.2 + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + path-type@4.0.0: {} pathe@1.1.2: {} pathval@1.1.1: {} + peberminta@0.9.0: {} + picocolors@1.0.0: {} picocolors@1.1.0: {} @@ -8994,6 +9618,8 @@ snapshots: mlly: 1.6.1 pathe: 1.1.2 + postal-mime@2.7.3: {} + postcss@8.4.31: dependencies: nanoid: 3.3.7 @@ -9018,6 +9644,8 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.0 + prismjs@1.29.0: {} + promise@7.3.1: dependencies: asap: 2.0.6 @@ -9028,6 +9656,8 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + proto-list@1.2.4: {} + pseudomap@1.0.2: {} psl@1.9.0: {} @@ -9065,6 +9695,10 @@ snapshots: react-is@18.3.0: {} + react-promise-suspense@0.3.4: + dependencies: + fast-deep-equal: 2.0.1 + react-refresh@0.14.0: {} react-remove-scroll-bar@2.3.4(@types/react@18.2.25)(react@18.3.1): @@ -9135,7 +9769,7 @@ snapshots: relay-runtime@12.0.0: dependencies: - '@babel/runtime': 7.23.1 + '@babel/runtime': 7.25.6 fbjs: 3.0.5 invariant: 2.2.4 transitivePeerDependencies: @@ -9153,6 +9787,13 @@ snapshots: requires-port@1.0.0: {} + resend@6.9.4(@react-email/render@0.0.15): + dependencies: + postal-mime: 2.7.3 + svix: 1.86.0 + optionalDependencies: + '@react-email/render': 0.0.15 + resolve-from@4.0.0: {} resolve-from@5.0.0: {} @@ -9243,14 +9884,17 @@ snapshots: scuid@1.1.0: {} + selderee@0.11.0: + dependencies: + parseley: 0.12.1 + semver@6.3.1: {} semver@7.5.4: dependencies: lru-cache: 6.0.0 - semver@7.7.3: - optional: true + semver@7.7.3: {} sentence-case@3.0.4: dependencies: @@ -9367,6 +10011,11 @@ snapshots: stackback@0.0.2: {} + standardwebhooks@1.0.0: + dependencies: + '@stablelib/base64': 1.0.1 + fast-sha256: 1.3.0 + statuses@2.0.1: {} std-env@3.7.0: {} @@ -9381,6 +10030,12 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + string.prototype.matchall@4.0.10: dependencies: call-bind: 1.0.2 @@ -9419,6 +10074,10 @@ snapshots: dependencies: ansi-regex: 5.0.1 + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + strip-bom@3.0.0: {} strip-final-newline@3.0.0: {} @@ -9429,12 +10088,12 @@ snapshots: dependencies: js-tokens: 9.0.0 - styled-jsx@5.1.6(@babel/core@7.23.0)(react@18.3.1): + styled-jsx@5.1.6(@babel/core@7.24.0)(react@18.3.1): dependencies: client-only: 0.0.1 react: 18.3.1 optionalDependencies: - '@babel/core': 7.23.0 + '@babel/core': 7.24.0 supports-color@5.5.0: dependencies: @@ -9446,6 +10105,11 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + svix@1.86.0: + dependencies: + standardwebhooks: 1.0.0 + uuid: 10.0.0 + swap-case@2.0.2: dependencies: tslib: 2.5.3 @@ -9660,6 +10324,8 @@ snapshots: util-deprecate@1.0.2: {} + uuid@10.0.0: {} + value-or-promise@1.0.12: {} vite-node@1.5.2(@types/node@18.18.3): @@ -9838,6 +10504,12 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + wrappy@1.0.2: {} ws@8.13.0: {} diff --git a/src/lib/settings.ts b/src/lib/settings.ts new file mode 100644 index 0000000..d92dc08 --- /dev/null +++ b/src/lib/settings.ts @@ -0,0 +1,133 @@ +import { promises as fs } from "fs"; +import path from "path"; + +export interface WebhookSettings { + orderCreated: { + enabled: boolean; + sendAdminNotification: boolean; + sendCustomerNotification: boolean; + }; + orderFulfilled: { + enabled: boolean; + sendAdminNotification: boolean; + sendCustomerNotification: boolean; + }; + orderCancelled: { + enabled: boolean; + sendAdminNotification: boolean; + sendCustomerNotification: boolean; + }; +} + +export interface EmailSettings { + fromEmail: string; + fromName: string; + adminEmails: string[]; + siteUrl: string; + dashboardUrl: string; +} + +export interface AppSettings { + webhooks: WebhookSettings; + email: EmailSettings; + updatedAt: string; +} + +const DEFAULT_SETTINGS: AppSettings = { + webhooks: { + orderCreated: { + enabled: true, + sendAdminNotification: true, + sendCustomerNotification: true, + }, + orderFulfilled: { + enabled: true, + sendAdminNotification: true, + sendCustomerNotification: true, + }, + orderCancelled: { + enabled: true, + sendAdminNotification: true, + sendCustomerNotification: true, + }, + }, + email: { + fromEmail: process.env.FROM_EMAIL || "support@mail.manoonoils.com", + fromName: process.env.FROM_NAME || "ManoonOils", + adminEmails: process.env.ADMIN_EMAILS?.split(",").map((e) => e.trim()).filter(Boolean) || [], + siteUrl: process.env.SITE_URL || "https://manoonoils.com", + dashboardUrl: process.env.DASHBOARD_URL || "https://dashboard.manoonoils.com", + }, + updatedAt: new Date().toISOString(), +}; + +class SettingsStore { + private filePath: string; + private settings: AppSettings | null = null; + + constructor() { + this.filePath = process.env.SETTINGS_FILE_PATH || "/tmp/.app-settings.json"; + } + + private async ensureFileExists(): Promise { + try { + await fs.access(this.filePath); + } catch { + const dir = path.dirname(this.filePath); + await fs.mkdir(dir, { recursive: true }); + await fs.writeFile(this.filePath, JSON.stringify(DEFAULT_SETTINGS, null, 2)); + } + } + + async get(): Promise { + if (this.settings) { + return this.settings; + } + + try { + await this.ensureFileExists(); + const data = await fs.readFile(this.filePath, "utf-8"); + this.settings = JSON.parse(data) as AppSettings; + return this.settings!; + } catch (error) { + console.error("Error reading settings:", error); + this.settings = DEFAULT_SETTINGS; + return this.settings; + } + } + + async set(newSettings: Partial): Promise { + const current = await this.get(); + const updated: AppSettings = { + ...current, + ...newSettings, + updatedAt: new Date().toISOString(), + }; + + try { + await fs.writeFile(this.filePath, JSON.stringify(updated, null, 2)); + this.settings = updated; + return updated; + } catch (error) { + console.error("Error writing settings:", error); + throw error; + } + } + + async isWebhookEnabled(webhook: keyof WebhookSettings): Promise { + const settings = await this.get(); + return settings.webhooks[webhook].enabled; + } + + async shouldSendAdminNotification(webhook: keyof WebhookSettings): Promise { + const settings = await this.get(); + return settings.webhooks[webhook].sendAdminNotification; + } + + async shouldSendCustomerNotification(webhook: keyof WebhookSettings): Promise { + const settings = await this.get(); + return settings.webhooks[webhook].sendCustomerNotification; + } +} + +export const settingsStore = new SettingsStore(); diff --git a/src/pages/api/settings.ts b/src/pages/api/settings.ts new file mode 100644 index 0000000..6be3a8b --- /dev/null +++ b/src/pages/api/settings.ts @@ -0,0 +1,34 @@ +import { NextRequest, NextResponse } from "next/server"; +import { settingsStore, AppSettings } from "@/lib/settings"; + +export async function GET() { + try { + const settings = await settingsStore.get(); + return NextResponse.json(settings); + } catch (error) { + console.error("Error getting settings:", error); + return NextResponse.json({ error: "Failed to get settings" }, { status: 500 }); + } +} + +export async function PUT(request: NextRequest) { + try { + const body = await request.json() as Partial; + const settings = await settingsStore.set(body); + return NextResponse.json(settings); + } catch (error) { + console.error("Error updating settings:", error); + return NextResponse.json({ error: "Failed to update settings" }, { status: 500 }); + } +} + +export async function PATCH(request: NextRequest) { + try { + const body = await request.json() as Partial; + const settings = await settingsStore.set(body); + return NextResponse.json(settings); + } catch (error) { + console.error("Error patching settings:", error); + return NextResponse.json({ error: "Failed to patch settings" }, { status: 500 }); + } +} diff --git a/src/pages/api/webhooks/order-cancelled.ts b/src/pages/api/webhooks/order-cancelled.ts index 3102941..47fa6be 100644 --- a/src/pages/api/webhooks/order-cancelled.ts +++ b/src/pages/api/webhooks/order-cancelled.ts @@ -5,6 +5,7 @@ import { } from "@/generated/graphql"; import { saleorApp } from "@/saleor-app"; import { sendOrderCancelledEmail, formatPrice } from "@/lib/resend"; +import { settingsStore } from "@/lib/settings"; export const orderCancelledWebhook = new SaleorAsyncWebhook({ name: "Order Cancelled in Saleor", @@ -23,7 +24,17 @@ export default orderCancelledWebhook.createHandler(async (req, res, ctx) => { return res.status(200).end(); } - console.log(`Order ${order.number} cancelled for customer: ${order.userEmail}`); + const webhookEnabled = await settingsStore.isWebhookEnabled("orderCancelled"); + const sendAdmin = await settingsStore.shouldSendAdminNotification("orderCancelled"); + const sendCustomer = await settingsStore.shouldSendCustomerNotification("orderCancelled"); + + console.log(`❌ Order ${order.number} cancelled for customer: ${order.userEmail}`); + console.log(`📋 Webhook settings - enabled: ${webhookEnabled}, admin: ${sendAdmin}, customer: ${sendCustomer}`); + + if (!webhookEnabled) { + console.log("⏭️ Webhook disabled, skipping notifications"); + return res.status(200).end(); + } const items = ((order as any).lines || []).map((line: any) => ({ id: line.id, @@ -42,16 +53,36 @@ export default orderCancelledWebhook.createHandler(async (req, res, ctx) => { total: formatPrice((order as any).total?.gross?.amount || 0, (order as any).total?.gross?.currency || "USD"), }; - try { - if (order.userEmail) { - await sendOrderCancelledEmail({ - to: order.userEmail, - orderData, - }); - console.log(`Customer notification sent for cancelled order ${order.number}`); + if (sendAdmin) { + try { + const adminEmails = process.env.ADMIN_EMAILS?.split(",").map(e => e.trim()).filter(e => e) || []; + + if (adminEmails.length > 0) { + await sendOrderCancelledEmail({ + to: adminEmails, + orderData, + }); + console.log(`✅ Admin notification sent for cancelled order ${order.number}`); + } + } catch (error) { + console.error("❌ Failed to send admin email:", error); } - } catch (error) { - console.error("Failed to send email:", error); + } + + if (sendCustomer) { + try { + if (order.userEmail) { + await sendOrderCancelledEmail({ + to: order.userEmail, + orderData, + }); + console.log(`✅ Customer notification sent for cancelled order ${order.number}`); + } + } catch (error) { + console.error("❌ Failed to send customer email:", error); + } + } else { + console.log("⏭️ Customer notification disabled, skipping"); } return res.status(200).end(); diff --git a/src/pages/api/webhooks/order-created.ts b/src/pages/api/webhooks/order-created.ts index bdec28b..b790b08 100644 --- a/src/pages/api/webhooks/order-created.ts +++ b/src/pages/api/webhooks/order-created.ts @@ -5,6 +5,7 @@ import { } from "@/generated/graphql"; import { saleorApp } from "@/saleor-app"; import { sendOrderConfirmationEmail, formatPrice } from "@/lib/resend"; +import { settingsStore } from "@/lib/settings"; export const orderCreatedWebhook = new SaleorAsyncWebhook({ name: "Order Created in Saleor", @@ -23,7 +24,17 @@ export default orderCreatedWebhook.createHandler(async (req, res, ctx) => { return res.status(200).end(); } + const webhookEnabled = await settingsStore.isWebhookEnabled("orderCreated"); + const sendAdmin = await settingsStore.shouldSendAdminNotification("orderCreated"); + const sendCustomer = await settingsStore.shouldSendCustomerNotification("orderCreated"); + console.log(`🎉 Order #${order.number} created for customer: ${order.userEmail} (${order.languageCode || "EN"})`); + console.log(`📋 Webhook settings - enabled: ${webhookEnabled}, admin: ${sendAdmin}, customer: ${sendCustomer}`); + + if (!webhookEnabled) { + console.log("⏭️ Webhook disabled, skipping notifications"); + return res.status(200).end(); + } const items = ((order as any).lines || []).map((line: any) => ({ id: line.id, @@ -50,38 +61,44 @@ export default orderCreatedWebhook.createHandler(async (req, res, ctx) => { phone: (order as any).shippingAddress?.phone, }; - // Send admin notification - try { - const adminEmails = process.env.ADMIN_EMAILS?.split(",").map(e => e.trim()).filter(e => e) || []; - - if (adminEmails.length > 0) { - await sendOrderConfirmationEmail({ - to: adminEmails, - orderData, - isAdmin: true, - }); - console.log(`✅ Admin notification sent for order #${order.number} to: ${adminEmails.join(", ")}`); - } else { - console.log("⚠️ No admin emails configured, skipping admin notification"); + if (sendAdmin) { + try { + const adminEmails = process.env.ADMIN_EMAILS?.split(",").map(e => e.trim()).filter(e => e) || []; + + if (adminEmails.length > 0) { + await sendOrderConfirmationEmail({ + to: adminEmails, + orderData, + isAdmin: true, + }); + console.log(`✅ Admin notification sent for order #${order.number} to: ${adminEmails.join(", ")}`); + } else { + console.log("⚠️ No admin emails configured, skipping admin notification"); + } + } catch (error) { + console.error("❌ Failed to send admin email:", error); } - } catch (error) { - console.error("❌ Failed to send admin email:", error); + } else { + console.log("⏭️ Admin notification disabled, skipping"); } - // Send customer confirmation - try { - if (order.userEmail) { - await sendOrderConfirmationEmail({ - to: order.userEmail, - orderData, - isAdmin: false, - }); - console.log(`✅ Customer confirmation sent for order #${order.number} to: ${order.userEmail}`); - } else { - console.log("⚠️ No customer email found, skipping customer notification"); + if (sendCustomer) { + try { + if (order.userEmail) { + await sendOrderConfirmationEmail({ + to: order.userEmail, + orderData, + isAdmin: false, + }); + console.log(`✅ Customer confirmation sent for order #${order.number} to: ${order.userEmail}`); + } else { + console.log("⚠️ No customer email found, skipping customer notification"); + } + } catch (error) { + console.error("❌ Failed to send customer email:", error); } - } catch (error) { - console.error("❌ Failed to send customer email:", error); + } else { + console.log("⏭️ Customer notification disabled, skipping"); } return res.status(200).end(); diff --git a/src/pages/api/webhooks/order-fulfilled.ts b/src/pages/api/webhooks/order-fulfilled.ts index 3a09d04..44c42e5 100644 --- a/src/pages/api/webhooks/order-fulfilled.ts +++ b/src/pages/api/webhooks/order-fulfilled.ts @@ -5,6 +5,7 @@ import { } from "@/generated/graphql"; import { saleorApp } from "@/saleor-app"; import { sendOrderShippedEmail, formatPrice } from "@/lib/resend"; +import { settingsStore } from "@/lib/settings"; export const orderFulfilledWebhook = new SaleorAsyncWebhook({ name: "Order Fulfilled in Saleor", @@ -23,7 +24,17 @@ export default orderFulfilledWebhook.createHandler(async (req, res, ctx) => { return res.status(200).end(); } - console.log(`Order ${order.number} fulfilled for customer: ${order.userEmail}`); + const webhookEnabled = await settingsStore.isWebhookEnabled("orderFulfilled"); + const sendAdmin = await settingsStore.shouldSendAdminNotification("orderFulfilled"); + const sendCustomer = await settingsStore.shouldSendCustomerNotification("orderFulfilled"); + + console.log(`📦 Order ${order.number} fulfilled for customer: ${order.userEmail}`); + console.log(`📋 Webhook settings - enabled: ${webhookEnabled}, admin: ${sendAdmin}, customer: ${sendCustomer}`); + + if (!webhookEnabled) { + console.log("⏭️ Webhook disabled, skipping notifications"); + return res.status(200).end(); + } const items = ((order as any).lines || []).map((line: any) => ({ id: line.id, @@ -41,16 +52,36 @@ export default orderFulfilledWebhook.createHandler(async (req, res, ctx) => { items, }; - try { - if (order.userEmail) { - await sendOrderShippedEmail({ - to: order.userEmail, - orderData, - }); - console.log(`Customer notification sent for fulfilled order ${order.number}`); + if (sendAdmin) { + try { + const adminEmails = process.env.ADMIN_EMAILS?.split(",").map(e => e.trim()).filter(e => e) || []; + + if (adminEmails.length > 0) { + await sendOrderShippedEmail({ + to: adminEmails, + orderData, + }); + console.log(`✅ Admin notification sent for fulfilled order ${order.number}`); + } + } catch (error) { + console.error("❌ Failed to send admin email:", error); } - } catch (error) { - console.error("Failed to send email:", error); + } + + if (sendCustomer) { + try { + if (order.userEmail) { + await sendOrderShippedEmail({ + to: order.userEmail, + orderData, + }); + console.log(`✅ Customer notification sent for fulfilled order ${order.number}`); + } + } catch (error) { + console.error("❌ Failed to send customer email:", error); + } + } else { + console.log("⏭️ Customer notification disabled, skipping"); } return res.status(200).end(); diff --git a/src/pages/settings.tsx b/src/pages/settings.tsx new file mode 100644 index 0000000..6c9bb26 --- /dev/null +++ b/src/pages/settings.tsx @@ -0,0 +1,410 @@ +import { Box, Button, Input, Text } from "@saleor/macaw-ui"; +import { NextPage } from "next"; +import { useCallback, useEffect, useState } from "react"; + +interface WebhookToggle { + enabled: boolean; + sendAdminNotification: boolean; + sendCustomerNotification: boolean; +} + +interface WebhookSettings { + orderCreated: WebhookToggle; + orderFulfilled: WebhookToggle; + orderCancelled: WebhookToggle; +} + +interface EmailSettings { + fromEmail: string; + fromName: string; + adminEmails: string; + siteUrl: string; + dashboardUrl: string; +} + +interface Settings { + webhooks: WebhookSettings; + email: EmailSettings; + updatedAt: string; +} + +const defaultSettings: Settings = { + webhooks: { + orderCreated: { enabled: true, sendAdminNotification: true, sendCustomerNotification: true }, + orderFulfilled: { enabled: true, sendAdminNotification: true, sendCustomerNotification: true }, + orderCancelled: { enabled: true, sendAdminNotification: true, sendCustomerNotification: true }, + }, + email: { + fromEmail: "", + fromName: "", + adminEmails: "", + siteUrl: "", + dashboardUrl: "", + }, + updatedAt: "", +}; + +const webhookLabels: Record = { + orderCreated: { + title: "Order Created", + description: "Send email notifications when a new order is placed", + }, + orderFulfilled: { + title: "Order Fulfilled", + description: "Send email notifications when an order is shipped", + }, + orderCancelled: { + title: "Order Cancelled", + description: "Send email notifications when an order is cancelled", + }, +}; + +const Toggle: React.FC<{ + checked: boolean; + onChange: (checked: boolean) => void; + disabled?: boolean; +}> = ({ checked, onChange, disabled }) => ( + +); + +const Card: React.FC<{ + children: React.ReactNode; + style?: React.CSSProperties; +}> = ({ children, style }) => ( +
+ {children} +
+); + +const SettingsPage: NextPage = () => { + const [settings, setSettings] = useState(defaultSettings); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [saved, setSaved] = useState(false); + const [error, setError] = useState(null); + + const bgColor = "#f9fafb"; + const textColor = "#111827"; + const subtextColor = "#6b7280"; + const borderColor = "#e5e7eb"; + + useEffect(() => { + fetch("/api/settings") + .then((res) => res.json()) + .then((data: Settings) => { + setSettings({ + ...data, + email: { + ...data.email, + adminEmails: Array.isArray(data.email.adminEmails) + ? data.email.adminEmails.join(", ") + : data.email.adminEmails || "", + }, + }); + setLoading(false); + }) + .catch((err) => { + console.error("Failed to load settings:", err); + setError("Failed to load settings"); + setLoading(false); + }); + }, []); + + const handleWebhookChange = useCallback( + (webhook: keyof WebhookSettings, field: keyof WebhookToggle) => { + setSettings((prev) => ({ + ...prev, + webhooks: { + ...prev.webhooks, + [webhook]: { + ...prev.webhooks[webhook], + [field]: !prev.webhooks[webhook][field], + }, + }, + })); + setSaved(false); + }, + [] + ); + + const handleEmailChange = useCallback((field: keyof EmailSettings, value: string) => { + setSettings((prev) => ({ + ...prev, + email: { + ...prev.email, + [field]: value, + }, + })); + setSaved(false); + }, []); + + const handleSave = useCallback(async () => { + setSaving(true); + setError(null); + setSaved(false); + + try { + const toSave = { + ...settings, + email: { + ...settings.email, + adminEmails: settings.email.adminEmails + .split(",") + .map((e) => e.trim()) + .filter(Boolean), + }, + }; + + const res = await fetch("/api/settings", { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(toSave), + }); + + if (!res.ok) { + throw new Error("Failed to save settings"); + } + + setSaved(true); + setTimeout(() => setSaved(false), 3000); + } catch (err) { + console.error("Failed to save settings:", err); + setError("Failed to save settings. Please try again."); + } finally { + setSaving(false); + } + }, [settings]); + + if (loading) { + return ( + + + Loading... + + + ); + } + + return ( + + + + Email Notifications + + + Configure when to send email notifications for order events + + + + {error && ( + + {error} + + )} + + + + Webhooks + + + {(Object.keys(webhookLabels) as Array).map((webhook) => { + const { title, description } = webhookLabels[webhook]; + const webhookSettings = settings.webhooks[webhook]; + + return ( + + + + + {title} + + + {description} + + + handleWebhookChange(webhook, "enabled")} + /> + + + {webhookSettings.enabled && ( + + + + Send to admin emails + + handleWebhookChange(webhook, "sendAdminNotification")} + /> + + + + Send to customer + + handleWebhookChange(webhook, "sendCustomerNotification")} + /> + + + )} + + ); + })} + + + + + Email Configuration + + + + + + From Name + + handleEmailChange("fromName", e.target.value)} + placeholder="ManoonOils" + style={{ width: "100%" }} + /> + + + + + From Email + + handleEmailChange("fromEmail", e.target.value)} + placeholder="support@mail.manoonoils.com" + style={{ width: "100%" }} + /> + + + + + Admin Emails (comma separated) + + handleEmailChange("adminEmails", e.target.value)} + placeholder="admin@example.com, manager@example.com" + style={{ width: "100%" }} + /> + + + + + Store URL + + handleEmailChange("siteUrl", e.target.value)} + placeholder="https://manoonoils.com" + style={{ width: "100%" }} + /> + + + + + Dashboard URL + + handleEmailChange("dashboardUrl", e.target.value)} + placeholder="https://dashboard.manoonoils.com" + style={{ width: "100%" }} + /> + + + + + + + {saved && ( + + Settings saved successfully! + + )} + + + {settings.updatedAt && ( + + Last updated: {new Date(settings.updatedAt).toLocaleString()} + + )} + + ); +}; + +export default SettingsPage;