From 0e8865364201bfc261fcfd6172dafb56cbfdbb42 Mon Sep 17 00:00:00 2001 From: Antony David Date: Mon, 15 Jul 2024 18:55:53 +0200 Subject: [PATCH] feat: setup resend (#235) --- .env.example | 2 + .../(dashboard)/dashboard/assemblies/page.tsx | 16 +- .../(dashboard)/dashboard/assemblies/utils.ts | 12 +- apps/admin/package.json | 2 +- apps/api/package.json | 1 + apps/api/src/handlers/assemblies.ts | 5 + apps/api/src/handlers/auth.ts | 5 + apps/api/src/libs/email.ts | 103 ++++++++ apps/api/vitest.config.ts | 2 +- apps/client/package.json | 2 +- packages/ui/package.json | 2 +- pnpm-lock.yaml | 229 +++++++++++++++++- 12 files changed, 358 insertions(+), 23 deletions(-) create mode 100644 apps/api/src/libs/email.ts diff --git a/.env.example b/.env.example index 8b577668..76b89f81 100644 --- a/.env.example +++ b/.env.example @@ -13,3 +13,5 @@ ATHLONIX_API_URL= NEXT_PUBLIC_API_URL= STRIPE_API_KEY=sk_test_xxx STRIPE_WEBHOOK_SECRET=whsec_xxx +RESEND_KEY=re_xxx +ENABLE_EMAILS='false' diff --git a/apps/admin/app/(dashboard)/dashboard/assemblies/page.tsx b/apps/admin/app/(dashboard)/dashboard/assemblies/page.tsx index 530f333f..da32972d 100644 --- a/apps/admin/app/(dashboard)/dashboard/assemblies/page.tsx +++ b/apps/admin/app/(dashboard)/dashboard/assemblies/page.tsx @@ -12,6 +12,7 @@ import { import { Input } from '@ui/components/ui/input'; import { Label } from '@ui/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@ui/components/ui/select'; +import { toast } from '@ui/components/ui/sonner'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@ui/components/ui/table'; import { Textarea } from '@ui/components/ui/textarea'; import { EditIcon } from 'lucide-react'; @@ -46,10 +47,15 @@ export default function AssembliesPage(): JSX.Element { async function handleAddAssembly(event: React.FormEvent) { event.preventDefault(); const formData = new FormData(event.currentTarget); - await createAssembly(formData); - const assemblies = await getAssemblies(); - setAssemblies(assemblies.data); - setCount(assemblies.count); + try { + await createAssembly(formData); + const assemblies = await getAssemblies(); + setAssemblies(assemblies.data); + setCount(assemblies.count); + toast.success("L'assemblée a été créée avec succès"); + } catch (_error) { + toast.error("Erreur lors de la création de l'assemblée"); + } setOpen(false); } @@ -119,7 +125,7 @@ export default function AssembliesPage(): JSX.Element { /> - diff --git a/apps/admin/app/(dashboard)/dashboard/assemblies/utils.ts b/apps/admin/app/(dashboard)/dashboard/assemblies/utils.ts index 0bdfada3..4dec04d8 100644 --- a/apps/admin/app/(dashboard)/dashboard/assemblies/utils.ts +++ b/apps/admin/app/(dashboard)/dashboard/assemblies/utils.ts @@ -48,12 +48,16 @@ export async function createAssembly(formData: FormData): Promise { const data = { name: formData.get('name'), description: formData.get('description'), - date: formData.get('date'), + date: new Date(formData.get('date') as string).toISOString(), location: Number(formData.get('location')) || null, lawsuit: null, }; const token = cookies().get('access_token')?.value; - await fetch(`${urlApi}/assemblies`, { + if (!token) { + throw new Error('No token found'); + } + + const resp = await fetch(`${urlApi}/assemblies`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -61,6 +65,10 @@ export async function createAssembly(formData: FormData): Promise { }, body: JSON.stringify(data), }); + + if (!resp.ok) { + throw new Error('Failed to create assembly'); + } } export async function updateAssembly(formData: FormData, id: number): Promise { diff --git a/apps/admin/package.json b/apps/admin/package.json index 354c1c5b..e6d56c78 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -33,7 +33,7 @@ "@types/react-dom": "^18.3.0", "autoprefixer": "^10.4.19", "postcss": "^8.4.39", - "tailwindcss": "^3.4.4", + "tailwindcss": "^3.4.5", "typescript": "^5.5.3" } } diff --git a/apps/api/package.json b/apps/api/package.json index 5c7c35bc..c843965b 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -23,6 +23,7 @@ "@repo/types": "workspace:*", "@supabase/supabase-js": "^2.44.4", "hono": "^4.4.13", + "resend": "^3.4.0", "socket.io": "^4.7.5", "stripe": "^16.2.0", "zod": "^3.23.8", diff --git a/apps/api/src/handlers/assemblies.ts b/apps/api/src/handlers/assemblies.ts index 6804ced9..12c5bbea 100644 --- a/apps/api/src/handlers/assemblies.ts +++ b/apps/api/src/handlers/assemblies.ts @@ -1,5 +1,6 @@ import crypto from 'node:crypto'; import { OpenAPIHono } from '@hono/zod-openapi'; +import { sendNewAssemblyEmail } from '../libs/email.js'; import { supabase } from '../libs/supabase.js'; import { zodErrorHook } from '../libs/zodError.js'; import { @@ -97,6 +98,10 @@ assemblies.openapi(createAssembly, async (c) => { closed: data.closed, }; + if (process.env.ENABLE_EMAILS === 'true') { + await sendNewAssemblyEmail(format.name, format.date, format.location); + } + return c.json(format, 201); }); diff --git a/apps/api/src/handlers/auth.ts b/apps/api/src/handlers/auth.ts index 38b9ca78..a1295fe4 100644 --- a/apps/api/src/handlers/auth.ts +++ b/apps/api/src/handlers/auth.ts @@ -1,6 +1,7 @@ import { OpenAPIHono } from '@hono/zod-openapi'; import { deleteCookie, getCookie, setCookie } from 'hono/cookie'; import { HTTPException } from 'hono/http-exception'; +import { sendWelcomeEmail } from '../libs/email.js'; import { supAdmin, supabase } from '../libs/supabase.js'; import { zodErrorHook } from '../libs/zodError.js'; import { loginUser, logoutUser, signupUser } from '../routes/auth.js'; @@ -48,6 +49,10 @@ auth.openapi(signupUser, async (c) => { return c.json({ error: 'Error while creating user' }, 400); } + if (process.env.ENABLE_EMAILS === 'true') { + await sendWelcomeEmail({ email: user.email, first_name: user.first_name }); + } + return c.json(user, 201); }); diff --git a/apps/api/src/libs/email.ts b/apps/api/src/libs/email.ts new file mode 100644 index 00000000..6eed0d2d --- /dev/null +++ b/apps/api/src/libs/email.ts @@ -0,0 +1,103 @@ +import type { Database } from '@repo/types'; +import { Resend } from 'resend'; +import { supabase } from './supabase.js'; + +type location = Database['public']['Tables']['ADDRESSES']['Row']; +type user = Database['public']['Tables']['USERS']['Row']; + +async function getApprovedMembers(): Promise { + const selectedRoles = ['MEMBER']; + const { data, error } = await supabase + .from('USERS') + .select('*, roles:ROLES!inner(id, name)') + .filter('deleted_at', 'is', null) + .eq('status', 'approved') + .in('roles.name', selectedRoles); + + if (error) { + return null; + } + + return data; +} + +export async function sendNewAssemblyEmail(name: string, date: string, location: location | null) { + const resend = new Resend(process.env.RESEND_KEY); + + if (!resend.emails) { + throw new Error('Emails feature is not enabled'); + } + + const members = await getApprovedMembers(); + if (!members) { + return; + } + + const formattedDate = new Date(date).toLocaleDateString('fr-FR', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric', + }); + + const address = + location === null + ? 'En ligne' + : `${location?.number} ${location?.road}, ${location?.city} ${location?.postal_code}`; + + for (const member of members) { + await resend.emails.send({ + from: 'onboarding@resend.dev', + to: member.email, + subject: 'ATHLONIX - Nouvelle Assemblée Générale', + html: ` + + +

Nouvelle Assemblée Générale : ${name}

+

Cher(e) ${member.first_name},

+

Nous avons le plaisir de vous informer qu'une nouvelle assemblée générale a été ajoutée au planning :

+
    +
  • Date : ${formattedDate}
  • +
  • Lieu : ${address}
  • +
+

Votre présence est importante pour notre association. Si vous ne pouvez pas assister à cette assemblée, merci de nous en informer dès que possible.

+

Cordialement,
L'équipe d'Athlonix

+ + + `, + }); + } +} + +export async function sendWelcomeEmail(user: { email: string; first_name: string }) { + const resend = new Resend(process.env.RESEND_KEY); + + if (!resend.emails) { + throw new Error('Emails feature is not enabled'); + } + + await resend.emails.send({ + from: 'onboarding@resend.dev', + to: user.email, + subject: 'Bienvenue chez Athlonix !', + html: ` + + +

Bienvenue chez Athlonix !

+

Cher(e) ${user.first_name},

+

Nous sommes ravis de vous accueillir au sein de notre association sportive. Votre compte a été créé avec succès.

+

Prochaines étapes :

+
    +
  1. Complétez votre profil en ligne
  2. +
  3. Explorez notre calendrier d'événements
  4. +
  5. Prenez connaissance de nos activités et cours proposés
  6. +
  7. Souscrivez à notre abonnement annuel pour devenir membre officiel
  8. +
+

Si vous avez des questions, n'hésitez pas à contacter notre équipe de support à support@votre-association.fr.

+

Nous vous souhaitons une excellente expérience parmi nous !

+

Sportivement,
L'équipe d'Athlonix

+ + + `, + }); +} diff --git a/apps/api/vitest.config.ts b/apps/api/vitest.config.ts index b3e8043c..c8e39b8e 100644 --- a/apps/api/vitest.config.ts +++ b/apps/api/vitest.config.ts @@ -16,7 +16,7 @@ export default defineConfig({ }, }, ignoreEmptyLines: true, - exclude: ['**/edm.ts', '**/stripe.ts', '**/storage.ts', ...coverageConfigDefaults.exclude], + exclude: ['**/edm.ts', '**/stripe.ts', '**/storage.ts', '**/email.ts', ...coverageConfigDefaults.exclude], reporter: ['text', 'html', 'json', 'json-summary'], }, }, diff --git a/apps/client/package.json b/apps/client/package.json index e93dc215..8e26d67c 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -33,7 +33,7 @@ "@types/react-dom": "^18.3.0", "autoprefixer": "^10.4.19", "postcss": "^8.4.39", - "tailwindcss": "^3.4.4", + "tailwindcss": "^3.4.5", "typescript": "^5.5.3" } } diff --git a/packages/ui/package.json b/packages/ui/package.json index 82485cac..79b9b820 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -27,7 +27,7 @@ "autoprefixer": "^10.4.19", "postcss": "^8.4.39", "react": "^18.3.1", - "tailwindcss": "^3.4.4", + "tailwindcss": "^3.4.5", "typescript": "^5.5.3" }, "dependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dba9d6cf..291e8f9d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -85,8 +85,8 @@ importers: specifier: ^8.4.39 version: 8.4.39 tailwindcss: - specifier: ^3.4.4 - version: 3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)) + specifier: ^3.4.5 + version: 3.4.5(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)) typescript: specifier: ^5.5.3 version: 5.5.3 @@ -114,6 +114,9 @@ importers: hono: specifier: ^4.4.13 version: 4.4.13 + resend: + specifier: ^3.4.0 + version: 3.4.0 socket.io: specifier: ^4.7.5 version: 4.7.5 @@ -216,8 +219,8 @@ importers: specifier: ^8.4.39 version: 8.4.39 tailwindcss: - specifier: ^3.4.4 - version: 3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)) + specifier: ^3.4.5 + version: 3.4.5(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)) typescript: specifier: ^5.5.3 version: 5.5.3 @@ -324,7 +327,7 @@ importers: version: 2.4.0 tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))) + version: 1.0.7(tailwindcss@3.4.5(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))) zod: specifier: ^3.23.8 version: 3.23.8 @@ -357,8 +360,8 @@ importers: specifier: ^18.3.1 version: 18.3.1 tailwindcss: - specifier: ^3.4.4 - version: 3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)) + specifier: ^3.4.5 + version: 3.4.5(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)) typescript: specifier: ^5.5.3 version: 5.5.3 @@ -900,6 +903,9 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@one-ini/wasm@0.1.1': + resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1746,6 +1752,10 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + '@react-email/render@0.0.15': + resolution: {integrity: sha512-/pT5dBu0y1mogrfEpc002rgRcXpbShK6PFtxTVU6LZZ+bccvZPgk67HKc01lxpa1eYGQgZ6I+VQ02GRnMDclTg==} + engines: {node: '>=18.0.0'} + '@react-stately/calendar@3.5.1': resolution: {integrity: sha512-7l7QhqGUJ5AzWHfvZzbTe3J4t72Ht5BmhW4hlVI7flQXtfrmYkVtl3ZdytEZkkHmWGYZRW9b4IQTQGZxhtlElA==} peerDependencies: @@ -2074,6 +2084,9 @@ packages: cpu: [x64] os: [win32] + '@selderee/plugin-htmlparser2@0.11.0': + resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} + '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -2226,6 +2239,10 @@ packages: '@vitest/utils@2.0.3': resolution: {integrity: sha512-c/UdELMuHitQbbc/EVctlBaxoYAwQPQdSNwv7z/vHyBKy2edYZaFgptE27BRueZB7eW8po+cllotMNTDpL3HWg==} + abbrev@2.0.0: + resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -2484,6 +2501,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@2.0.0: resolution: {integrity: sha512-eS0N9WwmjTqrOmR3o83F5vW8Z+9R1HnVz3xmzT2PMFug9ly+Au/fxRWlEBSb6LcZwspSsEn9Xs1uw9YgzAg1EQ==} @@ -2588,6 +2608,10 @@ packages: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} + 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==} @@ -2627,12 +2651,30 @@ packages: dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + 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@2.1.1: resolution: {integrity: sha512-HnM6ZlFqcajLsyudHq7LeeLDr2rFAVYtDv/hV5qchQEidSck8j9OPUsXY9KwJv/lHMtYlX4DjRQqwFYa+0r8Ug==} eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + editorconfig@1.0.4: + resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==} + engines: {node: '>=14'} + hasBin: true + electron-to-chromium@1.4.827: resolution: {integrity: sha512-VY+J0e4SFcNfQy19MEoMdaIcZLmDCprqvBtkii1WTCTQHpRvf5N8+3kTYCgL/PcntvwQvmMJWTuDPsq+IlhWKQ==} @@ -2653,6 +2695,10 @@ packages: 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'} + es-define-property@1.0.0: resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} engines: {node: '>= 0.4'} @@ -2710,6 +2756,9 @@ packages: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} + fast-deep-equal@2.0.1: + resolution: {integrity: sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==} + fast-equals@5.0.1: resolution: {integrity: sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==} engines: {node: '>=6.0.0'} @@ -2857,6 +2906,13 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + 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-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -3011,6 +3067,15 @@ packages: resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} hasBin: true + 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-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -3027,6 +3092,9 @@ packages: jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + leac@0.6.0: + resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} + lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} @@ -3120,6 +3188,10 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@9.0.1: + resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==} + engines: {node: '>=16 || 14 >=14.17'} + minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -3211,6 +3283,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@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -3287,6 +3364,9 @@ packages: param-case@2.1.1: resolution: {integrity: sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==} + parseley@0.12.1: + resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==} + pascal-case@2.0.1: resolution: {integrity: sha512-qjS4s8rBOJa2Xm0jmxXiyh1+OFf6ekCWOvUaRgAQSktzlTbMotS0nmG9gyYAybCWBcuP4fsBeRCKNwGBnMe2OQ==} @@ -3323,6 +3403,9 @@ packages: resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} engines: {node: '>= 14.16'} + peberminta@0.9.0: + resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} + picocolors@1.0.1: resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} @@ -3386,6 +3469,9 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + proxy-agent@6.4.0: resolution: {integrity: sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==} engines: {node: '>= 14'} @@ -3433,6 +3519,9 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-promise-suspense@0.3.4: + resolution: {integrity: sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==} + react-remove-scroll-bar@2.3.6: resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==} engines: {node: '>=10'} @@ -3524,6 +3613,10 @@ packages: resolution: {integrity: sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==} engines: {node: '>=0.10.0'} + resend@3.4.0: + resolution: {integrity: sha512-F3PVHdTHeLonSnrU5V6k8643LJ9QacLu3uI9M+BAFkmBmB1ELM2x7fdsziYZoSm6DmU6TKwiQCK0jf8dcNomcQ==} + engines: {node: '>=18'} + resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} @@ -3577,6 +3670,9 @@ packages: scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + selderee@0.11.0: + resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==} + semver@7.6.2: resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==} engines: {node: '>=10'} @@ -3763,8 +3859,8 @@ packages: peerDependencies: tailwindcss: '>=3.0.0 || insiders' - tailwindcss@3.4.4: - resolution: {integrity: sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==} + tailwindcss@3.4.5: + resolution: {integrity: sha512-DlTxttYcogpDfx3tf/8jfnma1nfAYi2cBUYV2YNoPPecwmO3YGiFlOX9D8tGAu+EDF38ryBzvrDKU/BLMsUwbw==} engines: {node: '>=14.0.0'} hasBin: true @@ -4501,6 +4597,8 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 + '@one-ini/wasm@0.1.1': {} + '@pkgjs/parseargs@0.11.0': optional: true @@ -5635,6 +5733,14 @@ snapshots: '@swc/helpers': 0.5.12 react: 18.3.1 + '@react-email/render@0.0.15': + 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-stately/calendar@3.5.1(react@18.3.1)': dependencies: '@internationalized/date': 3.5.4 @@ -6023,6 +6129,11 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.18.1': optional: true + '@selderee/plugin-htmlparser2@0.11.0': + dependencies: + domhandler: 5.0.3 + selderee: 0.11.0 + '@socket.io/component-emitter@3.1.2': {} '@supabase/auth-js@2.64.4': @@ -6248,6 +6359,8 @@ snapshots: loupe: 3.1.1 tinyrainbow: 1.2.0 + abbrev@2.0.0: {} + accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -6519,6 +6632,11 @@ snapshots: concat-map@0.0.1: {} + config-chain@1.1.13: + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + constant-case@2.0.0: dependencies: snake-case: 2.1.0 @@ -6599,6 +6717,8 @@ snapshots: deep-extend@0.6.0: {} + deepmerge@4.3.1: {} + defaults@1.0.4: dependencies: clone: 1.0.4 @@ -6645,12 +6765,37 @@ snapshots: '@babel/runtime': 7.24.8 csstype: 3.1.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@2.1.1: dependencies: no-case: 2.3.2 eastasianwidth@0.2.0: {} + editorconfig@1.0.4: + dependencies: + '@one-ini/wasm': 0.1.1 + commander: 10.0.1 + minimatch: 9.0.1 + semver: 7.6.2 + electron-to-chromium@1.4.827: {} emoji-regex@8.0.0: {} @@ -6688,6 +6833,8 @@ snapshots: - supports-color - utf-8-validate + entities@4.5.0: {} + es-define-property@1.0.0: dependencies: get-intrinsic: 1.2.4 @@ -6774,6 +6921,8 @@ snapshots: iconv-lite: 0.4.24 tmp: 0.0.33 + fast-deep-equal@2.0.1: {} + fast-equals@5.0.1: {} fast-glob@3.3.2: @@ -6942,6 +7091,21 @@ snapshots: html-escaper@2.0.2: {} + 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.1.0 + entities: 4.5.0 + http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.1 @@ -7106,6 +7270,16 @@ snapshots: jiti@1.21.6: {} + 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-tokens@4.0.0: {} js-tokens@9.0.0: {} @@ -7122,6 +7296,8 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + leac@0.6.0: {} + lilconfig@2.1.0: {} lilconfig@3.1.2: {} @@ -7202,6 +7378,10 @@ snapshots: dependencies: brace-expansion: 1.1.11 + minimatch@9.0.1: + dependencies: + brace-expansion: 2.0.1 + minimatch@9.0.5: dependencies: brace-expansion: 2.0.1 @@ -7297,6 +7477,10 @@ snapshots: node-releases@2.0.14: {} + nopt@7.2.1: + dependencies: + abbrev: 2.0.0 + normalize-path@3.0.0: {} normalize-range@0.1.2: {} @@ -7386,6 +7570,11 @@ snapshots: dependencies: no-case: 2.3.2 + parseley@0.12.1: + dependencies: + leac: 0.6.0 + peberminta: 0.9.0 + pascal-case@2.0.1: dependencies: camel-case: 3.0.0 @@ -7414,6 +7603,8 @@ snapshots: pathval@2.0.0: {} + peberminta@0.9.0: {} + picocolors@1.0.1: {} picomatch@2.3.1: {} @@ -7472,6 +7663,8 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + proto-list@1.2.4: {} + proxy-agent@6.4.0: dependencies: agent-base: 7.1.1 @@ -7561,6 +7754,10 @@ snapshots: react-is@16.13.1: {} + react-promise-suspense@0.3.4: + dependencies: + fast-deep-equal: 2.0.1 + react-remove-scroll-bar@2.3.6(@types/react@18.3.3)(react@18.3.1): dependencies: react: 18.3.1 @@ -7665,6 +7862,10 @@ snapshots: dependencies: rc: 1.2.8 + resend@3.4.0: + dependencies: + '@react-email/render': 0.0.15 + resolve-pkg-maps@1.0.0: {} resolve@1.22.8: @@ -7732,6 +7933,10 @@ snapshots: dependencies: loose-envify: 1.4.0 + selderee@0.11.0: + dependencies: + parseley: 0.12.1 + semver@7.6.2: {} sentence-case@2.1.1: @@ -7956,11 +8161,11 @@ snapshots: tailwind-merge@2.4.0: {} - tailwindcss-animate@1.0.7(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))): + tailwindcss-animate@1.0.7(tailwindcss@3.4.5(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))): dependencies: - tailwindcss: 3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)) + tailwindcss: 3.4.5(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)) - tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)): + tailwindcss@3.4.5(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2