diff --git a/package.json b/package.json index 3a1dae8..fcb33e3 100644 --- a/package.json +++ b/package.json @@ -24,9 +24,11 @@ "payload": "^3.77.0", "react": "19.2.3", "react-dom": "19.2.3", + "resend": "^6.9.2", "sharp": "^0.34.5" }, "devDependencies": { + "@eslint/eslintrc": "^3.3.3", "@swc-node/register": "^1.11.1", "@swc/core": "^1.15.11", "@tailwindcss/postcss": "^4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 06c95a6..ae5f663 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,10 +47,16 @@ importers: react-dom: specifier: 19.2.3 version: 19.2.3(react@19.2.3) + resend: + specifier: ^6.9.2 + version: 6.9.2 sharp: specifier: ^0.34.5 version: 0.34.5 devDependencies: + '@eslint/eslintrc': + specifier: ^3.3.3 + version: 3.3.3 '@swc-node/register': specifier: ^1.11.1 version: 1.11.1(@swc/core@1.15.11)(@swc/types@0.1.25)(typescript@5.9.3) @@ -1298,6 +1304,9 @@ packages: '@rushstack/eslint-patch@1.16.1': resolution: {integrity: sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==} + '@stablelib/base64@1.0.1': + resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==} + '@swc-node/core@1.14.1': resolution: {integrity: sha512-jrt5GUaZUU6cmMS+WTJEvGvaB6j1YNKPHPzC2PUi2BjaFbtxURHj6641Az6xN7b665hNniAIdvjxWcRml5yCnw==} engines: {node: '>= 10'} @@ -2384,6 +2393,9 @@ packages: fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fast-sha256@1.3.0: + resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==} + fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} @@ -3335,6 +3347,9 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} + 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} @@ -3543,6 +3558,15 @@ packages: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + resend@6.9.2: + resolution: {integrity: sha512-uIM6CQ08tS+hTCRuKBFbOBvHIGaEhqZe8s4FOgqsVXSbQLAhmNWpmUhG3UAtRnmcwTWFUqnHa/+Vux8YGPyDBA==} + 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'} @@ -3701,6 +3725,9 @@ packages: stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + standardwebhooks@1.0.0: + resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==} + state-local@1.0.7: resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} @@ -3794,6 +3821,9 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + svix@1.84.1: + resolution: {integrity: sha512-K8DPPSZaW/XqXiz1kEyzSHYgmGLnhB43nQCMeKjWGCUpLIpAMMM8kx3rVVOSm6Bo6EHyK1RQLPT4R06skM/MlQ==} + tabbable@6.4.0: resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} @@ -5166,6 +5196,8 @@ snapshots: '@rushstack/eslint-patch@1.16.1': {} + '@stablelib/base64@1.0.1': {} + '@swc-node/core@1.14.1(@swc/core@1.15.11)(@swc/types@0.1.25)': dependencies: '@swc/core': 1.15.11 @@ -6342,6 +6374,8 @@ snapshots: fast-safe-stringify@2.1.1: {} + fast-sha256@1.3.0: {} + fast-uri@3.1.0: {} fastq@1.20.1: @@ -7448,6 +7482,8 @@ snapshots: possible-typed-array-names@1.1.0: {} + postal-mime@2.7.3: {} + postcss@8.4.31: dependencies: nanoid: 3.3.11 @@ -7605,6 +7641,11 @@ snapshots: require-from-string@2.0.2: {} + resend@6.9.2: + dependencies: + postal-mime: 2.7.3 + svix: 1.84.1 + resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -7800,6 +7841,11 @@ snapshots: stable-hash@0.0.5: {} + standardwebhooks@1.0.0: + dependencies: + '@stablelib/base64': 1.0.1 + fast-sha256: 1.3.0 + state-local@1.0.7: {} stop-iteration-iterator@1.1.0: @@ -7905,6 +7951,11 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + svix@1.84.1: + dependencies: + standardwebhooks: 1.0.0 + uuid: 10.0.0 + tabbable@6.4.0: {} tailwindcss@4.2.0: {} diff --git a/src/app/contact/ContactForm.tsx b/src/app/contact/ContactForm.tsx new file mode 100644 index 0000000..cafa236 --- /dev/null +++ b/src/app/contact/ContactForm.tsx @@ -0,0 +1,151 @@ +'use client'; + +import { useActionState } from 'react'; +import { Button } from '@/components/ui/Button'; +import { submitContactForm } from './actions'; + +export function ContactForm() { + const [state, action, isPending] = useActionState(submitContactForm, null); + + if (state?.success) { + return ( +
+ We'll get back to you within 2 hours on business days. +
+| Name | +${firstName} ${lastName} | +
| ${email} | +|
| Phone | +${phone || '—'} | +
| Service | +${serviceLabel} | +
| Message | +${message} | +