This commit is contained in:
1277
package-lock.json
generated
1277
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -9,15 +9,15 @@
|
|||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/node": "^9.5.5",
|
"@astrojs/node": "^10.0.4",
|
||||||
"@astrojs/react": "^5.0.0",
|
"@astrojs/react": "^5.0.2",
|
||||||
"@astrojs/sitemap": "^3.2.1",
|
"@astrojs/sitemap": "^3.7.2",
|
||||||
"@fontsource-variable/geist": "^5.2.8",
|
"@fontsource-variable/geist": "^5.2.8",
|
||||||
"@headlessui/react": "^2.2.9",
|
"@headlessui/react": "^2.2.9",
|
||||||
"@heroicons/react": "^2.2.0",
|
"@heroicons/react": "^2.2.0",
|
||||||
"@tailwindcss/vite": "^4.0.0",
|
"@tailwindcss/vite": "^4.0.0",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"astro": "^5.4.0",
|
"astro": "^6.1.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"embla-carousel-autoplay": "^8.6.0",
|
"embla-carousel-autoplay": "^8.6.0",
|
||||||
|
|||||||
5
public/images/logo-text-highlighted.svg
Normal file
5
public/images/logo-text-highlighted.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 127 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 633 B |
@@ -12,13 +12,11 @@ interface Props {
|
|||||||
addressValue: string
|
addressValue: string
|
||||||
serviceAreaLabel: string
|
serviceAreaLabel: string
|
||||||
serviceArea: string
|
serviceArea: string
|
||||||
firstNameLabel?: string
|
fullNameLabel?: string
|
||||||
lastNameLabel?: string
|
|
||||||
emailFieldLabel?: string
|
emailFieldLabel?: string
|
||||||
phoneFieldLabel?: string
|
phoneFieldLabel?: string
|
||||||
messageLabel?: string
|
messageLabel?: string
|
||||||
firstNamePlaceholder?: string
|
fullNamePlaceholder?: string
|
||||||
lastNamePlaceholder?: string
|
|
||||||
emailPlaceholder?: string
|
emailPlaceholder?: string
|
||||||
phonePlaceholder?: string
|
phonePlaceholder?: string
|
||||||
messagePlaceholder?: string
|
messagePlaceholder?: string
|
||||||
@@ -26,8 +24,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ContactFields {
|
interface ContactFields {
|
||||||
firstName: string
|
fullName: string
|
||||||
lastName: string
|
|
||||||
email: string
|
email: string
|
||||||
phone: string
|
phone: string
|
||||||
message: string
|
message: string
|
||||||
@@ -42,13 +39,11 @@ export default function Contact({
|
|||||||
addressValue,
|
addressValue,
|
||||||
serviceAreaLabel,
|
serviceAreaLabel,
|
||||||
serviceArea,
|
serviceArea,
|
||||||
firstNameLabel = 'Prénom',
|
fullNameLabel = 'Nom complet',
|
||||||
lastNameLabel = 'Nom',
|
|
||||||
emailFieldLabel = 'Courriel',
|
emailFieldLabel = 'Courriel',
|
||||||
phoneFieldLabel = 'Téléphone',
|
phoneFieldLabel = 'Téléphone',
|
||||||
messageLabel = 'Message',
|
messageLabel = 'Message',
|
||||||
firstNamePlaceholder = 'Jean',
|
fullNamePlaceholder = 'Jean Tremblay',
|
||||||
lastNamePlaceholder = 'Tremblay',
|
|
||||||
emailPlaceholder = 'nom@exemple.com',
|
emailPlaceholder = 'nom@exemple.com',
|
||||||
phonePlaceholder = '(514) 555-1234',
|
phonePlaceholder = '(514) 555-1234',
|
||||||
messagePlaceholder = 'Décrivez votre projet...',
|
messagePlaceholder = 'Décrivez votre projet...',
|
||||||
@@ -184,32 +179,18 @@ export default function Contact({
|
|||||||
) : (
|
) : (
|
||||||
<form onSubmit={handleSubmit(onSubmit)} noValidate className="mt-0">
|
<form onSubmit={handleSubmit(onSubmit)} noValidate className="mt-0">
|
||||||
<div className="grid grid-cols-1 gap-x-8 gap-y-6 sm:grid-cols-2">
|
<div className="grid grid-cols-1 gap-x-8 gap-y-6 sm:grid-cols-2">
|
||||||
<div>
|
<div className="sm:col-span-2">
|
||||||
<label htmlFor="first-name" className={labelClass}>{firstNameLabel}</label>
|
<label htmlFor="full-name" className={labelClass}>{fullNameLabel}</label>
|
||||||
<div className="mt-2.5">
|
<div className="mt-2.5">
|
||||||
<input
|
<input
|
||||||
id="first-name"
|
id="full-name"
|
||||||
type="text"
|
type="text"
|
||||||
autoComplete="given-name"
|
autoComplete="name"
|
||||||
placeholder={firstNamePlaceholder}
|
placeholder={fullNamePlaceholder}
|
||||||
className={errors.firstName ? inputErrorClass : inputClass}
|
className={errors.fullName ? inputErrorClass : inputClass}
|
||||||
{...register("firstName", { required: "Veuillez entrer votre prénom." })}
|
{...register("fullName", { required: "Veuillez entrer votre nom complet." })}
|
||||||
/>
|
/>
|
||||||
{errors.firstName && <p className="mt-1.5 text-sm text-red-600 dark:text-red-400">{errors.firstName.message}</p>}
|
{errors.fullName && <p className="mt-1.5 text-sm text-red-600 dark:text-red-400">{errors.fullName.message}</p>}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label htmlFor="last-name" className={labelClass}>{lastNameLabel}</label>
|
|
||||||
<div className="mt-2.5">
|
|
||||||
<input
|
|
||||||
id="last-name"
|
|
||||||
type="text"
|
|
||||||
autoComplete="family-name"
|
|
||||||
placeholder={lastNamePlaceholder}
|
|
||||||
className={errors.lastName ? inputErrorClass : inputClass}
|
|
||||||
{...register("lastName", { required: "Veuillez entrer votre nom." })}
|
|
||||||
/>
|
|
||||||
{errors.lastName && <p className="mt-1.5 text-sm text-red-600 dark:text-red-400">{errors.lastName.message}</p>}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="sm:col-span-2">
|
<div className="sm:col-span-2">
|
||||||
@@ -238,7 +219,10 @@ export default function Contact({
|
|||||||
autoComplete="tel"
|
autoComplete="tel"
|
||||||
placeholder={phonePlaceholder}
|
placeholder={phonePlaceholder}
|
||||||
className={errors.phone ? inputErrorClass : inputClass}
|
className={errors.phone ? inputErrorClass : inputClass}
|
||||||
{...register("phone", { required: "Veuillez entrer votre numéro de téléphone." })}
|
{...register("phone", {
|
||||||
|
required: "Veuillez entrer votre numéro de téléphone.",
|
||||||
|
pattern: { value: /^[+\d][\d\s\-().]{6,19}$/, message: "Veuillez entrer un numéro de téléphone valide." },
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
{errors.phone && <p className="mt-1.5 text-sm text-red-600 dark:text-red-400">{errors.phone.message}</p>}
|
{errors.phone && <p className="mt-1.5 text-sm text-red-600 dark:text-red-400">{errors.phone.message}</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const canonicalUrl = canonical
|
|||||||
|
|
||||||
const ogImageUrl = ogImage
|
const ogImageUrl = ogImage
|
||||||
? ogImage.startsWith('http') ? ogImage : `${siteUrl}${ogImage}`
|
? ogImage.startsWith('http') ? ogImage : `${siteUrl}${ogImage}`
|
||||||
: `${siteUrl}/images/logo.png`;
|
: `${siteUrl}/images/logo-vignette.svg`;
|
||||||
|
|
||||||
// JSON-LD LocalBusiness structured data
|
// JSON-LD LocalBusiness structured data
|
||||||
const structuredData = {
|
const structuredData = {
|
||||||
@@ -92,7 +92,7 @@ const structuredData = {
|
|||||||
|
|
||||||
<!-- Favicon -->
|
<!-- Favicon -->
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<link rel="icon" type="image/png" href="/images/logo.png" />
|
<link rel="icon" type="image/png" href="/images/logo-vignette.svg" />
|
||||||
|
|
||||||
<!-- JSON-LD -->
|
<!-- JSON-LD -->
|
||||||
<script type="application/ld+json" set:html={JSON.stringify(structuredData)} />
|
<script type="application/ld+json" set:html={JSON.stringify(structuredData)} />
|
||||||
|
|||||||
@@ -16,16 +16,16 @@ export type HeroContent = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const logo = {
|
const logo = {
|
||||||
src: '/images/logo-text.svg',
|
src: "/images/logo-text.svg",
|
||||||
alt: 'Cachet Peintres Décorateurs',
|
alt: "Cachet Peintres Décorateurs",
|
||||||
}
|
};
|
||||||
|
|
||||||
const slides = [
|
const slides = [
|
||||||
{ src: '/images/carrousel-1.webp', alt: "Peinture intérieure" },
|
{ src: "/images/carrousel-1.webp", alt: "Peinture intérieure" },
|
||||||
{ src: '/images/carrousel-2.webp', alt: "Décoration murale" },
|
{ src: "/images/carrousel-2.webp", alt: "Décoration murale" },
|
||||||
{ src: '/images/carrousel-3.webp', alt: "Finition de qualité" },
|
{ src: "/images/carrousel-3.webp", alt: "Finition de qualité" },
|
||||||
{ src: '/images/carrousel-4.webp', alt: "Chambre" },
|
{ src: "/images/carrousel-4.webp", alt: "Chambre" },
|
||||||
{ src: '/images/carrousel-5.webp', alt: "Pièce lumineuse" },
|
{ src: "/images/carrousel-5.webp", alt: "Pièce lumineuse" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const autoplayPlugin = Autoplay({
|
const autoplayPlugin = Autoplay({
|
||||||
@@ -71,34 +71,106 @@ export default function Hero({ content }: { content: HeroContent }) {
|
|||||||
|
|
||||||
<div className="relative z-10 mx-auto max-w-7xl px-6 lg:px-8 h-full flex items-center justify-center pt-14">
|
<div className="relative z-10 mx-auto max-w-7xl px-6 lg:px-8 h-full flex items-center justify-center pt-14">
|
||||||
<div className="mx-auto max-w-4xl text-center">
|
<div className="mx-auto max-w-4xl text-center">
|
||||||
<motion.div {...fadeUp(0.2)}>
|
<div className="relative rounded-lg px-6 py-2">
|
||||||
<img
|
<motion.div
|
||||||
src={logo.src}
|
className="absolute inset-0 rounded-lg bg-brand-50"
|
||||||
alt={general.siteName}
|
initial={{ opacity: 0 }}
|
||||||
className="w-full h-auto"
|
animate={{ opacity: 1 }}
|
||||||
|
transition={{ delay: 0.85, duration: 2, ease: [0.22, 1, 0.36, 1] }}
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
<svg className="relative" viewBox="0 0 1390 500" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<defs>
|
||||||
|
<clipPath id="cachet-rise-clip">
|
||||||
|
<motion.rect
|
||||||
|
x="0"
|
||||||
|
initial={{ y: 334, height: 0 }}
|
||||||
|
animate={{ y: 0, height: 334 }}
|
||||||
|
transition={{
|
||||||
|
duration: 1,
|
||||||
|
ease: [0.22, 1, 0.36, 1],
|
||||||
|
delay: 0.85,
|
||||||
|
}}
|
||||||
|
width="1390"
|
||||||
|
/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath id="decorateurs-drop-clip">
|
||||||
|
<motion.rect
|
||||||
|
x="0"
|
||||||
|
initial={{ y: 334, height: 0 }}
|
||||||
|
animate={{ y: 334, height: 166 }}
|
||||||
|
transition={{
|
||||||
|
duration: 1,
|
||||||
|
ease: [0.22, 1, 0.36, 1],
|
||||||
|
delay: 0.85,
|
||||||
|
}}
|
||||||
|
width="1390"
|
||||||
|
/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<motion.text
|
||||||
|
x="3"
|
||||||
|
y="312"
|
||||||
|
font-family="Cocogoose Classic Trial"
|
||||||
|
font-size="350"
|
||||||
|
font-weight="600"
|
||||||
|
className="fill-brand-600"
|
||||||
|
clipPath="url(#cachet-rise-clip)"
|
||||||
|
>
|
||||||
|
CACHET
|
||||||
|
</motion.text>
|
||||||
|
<motion.path
|
||||||
|
id="Rectangle"
|
||||||
|
fill="#b0b0ac"
|
||||||
|
fill-rule="evenodd"
|
||||||
|
stroke="none"
|
||||||
|
d="M 228 360 L 1161 360 L 1161 334 L 228 334 Z"
|
||||||
|
initial={{ scaleX: 0 }}
|
||||||
|
animate={{ scaleX: 1 }}
|
||||||
|
transition={{
|
||||||
|
duration: 0.85,
|
||||||
|
ease: [0.22, 1, 0.36, 1],
|
||||||
|
delay: 0.35,
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
originX: 0,
|
||||||
|
transformBox: "fill-box",
|
||||||
|
transformOrigin: "0% 50%",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<text
|
||||||
|
x="228"
|
||||||
|
y="435"
|
||||||
|
font-family="Geoform"
|
||||||
|
font-size="72"
|
||||||
|
font-weight="700"
|
||||||
|
className="fill-graphite-600"
|
||||||
|
clipPath="url(#decorateurs-drop-clip)"
|
||||||
|
>
|
||||||
|
PEINTRES DECORATEURS
|
||||||
|
</text>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
<motion.p
|
<motion.p
|
||||||
{...fadeUp(0.45)}
|
{...fadeUp(1.15)}
|
||||||
className="p-4 mt-8 text-lg font-medium text-pretty text-white sm:text-xl/8 lg:text-2xl/8"
|
className="p-4 mt-8 text-lg font-medium text-pretty text-white sm:text-xl/8 lg:text-2xl/8"
|
||||||
>
|
>
|
||||||
{subtitle}
|
{subtitle}
|
||||||
</motion.p>
|
</motion.p>
|
||||||
|
|
||||||
<div
|
<motion.div
|
||||||
{...fadeUp(0.65)}
|
{...fadeUp(1.25)}
|
||||||
|
|
||||||
className="mt-10 flex items-center justify-center"
|
className="mt-10 flex items-center justify-center"
|
||||||
>
|
>
|
||||||
<motion.a
|
<motion.a
|
||||||
whileHover={{ scale: 1.075, transition: { duration: 0.1 } }}
|
whileHover={{ scale: 1.075, transition: { duration: 0.1 } }}
|
||||||
transition={{ type: "spring", stiffness: 400, damping: 25, }}
|
transition={{ type: "spring", stiffness: 400, damping: 25 }}
|
||||||
href={ctaHref}
|
href={ctaHref}
|
||||||
className="rounded-md bg-[var(--color-brand-600)] px-3.5 py-2.5 text-lg font-light text-white shadow-xs hover:bg-[var(--color-brand-700)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-brand-600)]"
|
className="rounded-md bg-[var(--color-brand-600)] px-3.5 py-2.5 text-lg font-light text-white shadow-xs hover:bg-[var(--color-brand-700)] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--color-brand-600)]"
|
||||||
>
|
>
|
||||||
{cta}
|
{cta}
|
||||||
</motion.a>
|
</motion.a>
|
||||||
</div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
"titleTemplate": "%s | Cachet Peintres Décorateurs",
|
"titleTemplate": "%s | Cachet Peintres Décorateurs",
|
||||||
"defaultDescription": "Peintres décorateurs professionnels à Laval et sur la Rive-Nord. Peinture résidentielle et commerciale, décoration intérieure et extérieure. RBQ 5839 8736 01. Soumission gratuite.",
|
"defaultDescription": "Peintres décorateurs professionnels à Laval et sur la Rive-Nord. Peinture résidentielle et commerciale, décoration intérieure et extérieure. RBQ 5839 8736 01. Soumission gratuite.",
|
||||||
"keywords": "peintre Laval, peinture résidentielle Laval, peinture commerciale Rive-Nord, décoration intérieure Laval, peintres décorateurs Québec, peinture extérieure Laval, rénovation Rive-Nord, soumission peinture gratuite",
|
"keywords": "peintre Laval, peinture résidentielle Laval, peinture commerciale Rive-Nord, décoration intérieure Laval, peintres décorateurs Québec, peinture extérieure Laval, rénovation Rive-Nord, soumission peinture gratuite",
|
||||||
"ogImage": "/images/logo.png",
|
"ogImage": "/images/logo-vignette.svg",
|
||||||
"twitterCard": "summary_large_image"
|
"twitterCard": "summary_large_image"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import general from '@/content/settings/general.json';
|
|||||||
export const prerender = false;
|
export const prerender = false;
|
||||||
|
|
||||||
interface ContactBody {
|
interface ContactBody {
|
||||||
firstName: string;
|
fullName: string;
|
||||||
lastName: string;
|
|
||||||
email: string;
|
email: string;
|
||||||
phone: string;
|
phone: string;
|
||||||
message: string;
|
message: string;
|
||||||
@@ -16,10 +15,9 @@ function isValidBody(body: unknown): body is ContactBody {
|
|||||||
if (typeof body !== 'object' || body === null) return false;
|
if (typeof body !== 'object' || body === null) return false;
|
||||||
const b = body as Record<string, unknown>;
|
const b = body as Record<string, unknown>;
|
||||||
return (
|
return (
|
||||||
typeof b.firstName === 'string' && b.firstName.trim().length > 0 &&
|
typeof b.fullName === 'string' && b.fullName.trim().length > 0 &&
|
||||||
typeof b.lastName === 'string' && b.lastName.trim().length > 0 &&
|
|
||||||
typeof b.email === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(b.email) &&
|
typeof b.email === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(b.email) &&
|
||||||
typeof b.phone === 'string' && b.phone.trim().length > 0 &&
|
typeof b.phone === 'string' && /^[+\d][\d\s\-().]{6,19}$/.test(b.phone.trim()) &&
|
||||||
typeof b.message === 'string' && b.message.trim().length > 0
|
typeof b.message === 'string' && b.message.trim().length > 0
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -37,14 +35,14 @@ function buildContactNotificationHtml(data: ContactBody): string {
|
|||||||
<div style="background:#ffffff;border-radius:0 0 8px 8px;overflow:hidden">
|
<div style="background:#ffffff;border-radius:0 0 8px 8px;overflow:hidden">
|
||||||
<div style="padding:24px;background:#f0f4ef;border-bottom:2px solid #d6e2d4">
|
<div style="padding:24px;background:#f0f4ef;border-bottom:2px solid #d6e2d4">
|
||||||
<p style="margin:0;font-size:15px;color:#314732;font-weight:600">
|
<p style="margin:0;font-size:15px;color:#314732;font-weight:600">
|
||||||
Vous avez reçu un nouveau message de <strong>${data.firstName} ${data.lastName}</strong>.
|
Vous avez reçu un nouveau message de <strong>${data.fullName}</strong>.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<table style="width:100%;border-collapse:collapse">
|
<table style="width:100%;border-collapse:collapse">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding:8px 12px;color:#666;font-size:14px;border-bottom:1px solid #eee;width:40%">Nom</td>
|
<td style="padding:8px 12px;color:#666;font-size:14px;border-bottom:1px solid #eee;width:40%">Nom</td>
|
||||||
<td style="padding:8px 12px;font-size:14px;border-bottom:1px solid #eee;font-weight:600">${data.firstName} ${data.lastName}</td>
|
<td style="padding:8px 12px;font-size:14px;border-bottom:1px solid #eee;font-weight:600">${data.fullName}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding:8px 12px;color:#666;font-size:14px;border-bottom:1px solid #eee">Courriel</td>
|
<td style="padding:8px 12px;color:#666;font-size:14px;border-bottom:1px solid #eee">Courriel</td>
|
||||||
@@ -61,7 +59,7 @@ function buildContactNotificationHtml(data: ContactBody): string {
|
|||||||
<div style="background:#f9fafb;border:1px solid #e5e7eb;border-radius:6px;padding:16px;font-size:14px;color:#374151;line-height:1.6;white-space:pre-wrap">${data.message}</div>
|
<div style="background:#f9fafb;border:1px solid #e5e7eb;border-radius:6px;padding:16px;font-size:14px;color:#374151;line-height:1.6;white-space:pre-wrap">${data.message}</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="padding:16px 20px 24px">
|
<div style="padding:16px 20px 24px">
|
||||||
<a href="mailto:${data.email}?subject=Re: Message — Cachet Peintres Décorateurs" style="display:inline-block;padding:10px 24px;background:#314732;color:#fff;text-decoration:none;border-radius:4px;font-size:14px;font-weight:700">Répondre à ${data.firstName}</a>
|
<a href="mailto:${data.email}?subject=Re: Message — Cachet Peintres Décorateurs" style="display:inline-block;padding:10px 24px;background:#314732;color:#fff;text-decoration:none;border-radius:4px;font-size:14px;font-weight:700">Répondre à ${data.fullName}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p style="margin:24px 0 0;text-align:center;font-size:11px;color:#999">
|
<p style="margin:24px 0 0;text-align:center;font-size:11px;color:#999">
|
||||||
@@ -105,7 +103,7 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
from: fromAddress,
|
from: fromAddress,
|
||||||
to: toAddress,
|
to: toAddress,
|
||||||
replyTo: body.email,
|
replyTo: body.email,
|
||||||
subject: `Nouveau message — ${body.firstName} ${body.lastName}`,
|
subject: `Nouveau message — ${body.fullName}`,
|
||||||
html: buildContactNotificationHtml(body),
|
html: buildContactNotificationHtml(body),
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
Reference in New Issue
Block a user