diff --git a/artifacts/api-server/package.json b/artifacts/api-server/package.json
index 6916f27..4b814b0 100644
--- a/artifacts/api-server/package.json
+++ b/artifacts/api-server/package.json
@@ -10,6 +10,7 @@
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"dependencies": {
+ "@getbrevo/brevo": "^5.0.4",
"@workspace/api-zod": "workspace:*",
"@workspace/db": "workspace:*",
"cookie-parser": "^1.4.7",
diff --git a/artifacts/api-server/src/routes/contact.ts b/artifacts/api-server/src/routes/contact.ts
new file mode 100644
index 0000000..429aa7f
--- /dev/null
+++ b/artifacts/api-server/src/routes/contact.ts
@@ -0,0 +1,73 @@
+import { Router, type IRouter } from "express";
+import { BrevoClient } from "@getbrevo/brevo";
+import { SendContactMessageBody } from "@workspace/api-zod";
+
+const router: IRouter = Router();
+
+function getBrevoClient() {
+ const apiKey = process.env.BREVO_API_KEY;
+ if (!apiKey) throw new Error("BREVO_API_KEY is not set");
+ return new BrevoClient({ apiKey });
+}
+
+router.post("/contact", async (req, res) => {
+ const parsed = SendContactMessageBody.safeParse(req.body);
+
+ if (!parsed.success) {
+ res.status(400).json({
+ success: false,
+ message: "Ungültige Eingabe. Bitte überprüfen Sie Ihre Angaben.",
+ });
+ return;
+ }
+
+ const { name, email, subject, message } = parsed.data;
+
+ try {
+ const brevo = getBrevoClient();
+
+ await brevo.transactionalEmails.sendTransacEmail({
+ sender: { name: "Kontaktformular Portfolio", email: "jh@unixweb.de" },
+ to: [{ email: "jh@unixweb.de", name: "Joachim Hummel" }],
+ replyTo: { email, name },
+ subject: subject ? `[Portfolio] ${subject}` : `[Portfolio] Neue Anfrage von ${name}`,
+ textContent: [
+ `Name: ${name}`,
+ `E-Mail: ${email}`,
+ subject ? `Betreff: ${subject}` : "",
+ "",
+ message,
+ ]
+ .filter((l) => l !== undefined)
+ .join("\n"),
+ htmlContent: `
+
Name: ${escapeHtml(name)}
+ E-Mail: ${escapeHtml(email)}
+ ${subject ? `Betreff: ${escapeHtml(subject)}
` : ""}
+
+ ${escapeHtml(message)}
+ `,
+ });
+
+ req.log.info({ to: "jh@unixweb.de", from: email }, "Contact message sent via Brevo");
+ res.json({ success: true, message: "Ihre Nachricht wurde erfolgreich gesendet." });
+ } catch (err) {
+ req.log.error({ err }, "Failed to send contact email via Brevo");
+ res.status(500).json({
+ success: false,
+ message:
+ "Die Nachricht konnte nicht gesendet werden. Bitte versuchen Sie es später erneut.",
+ });
+ }
+});
+
+function escapeHtml(text: string): string {
+ return text
+ .replace(/&/g, "&")
+ .replace(//g, ">")
+ .replace(/"/g, """)
+ .replace(/'/g, "'");
+}
+
+export default router;
diff --git a/artifacts/api-server/src/routes/index.ts b/artifacts/api-server/src/routes/index.ts
index 5a1f77a..5e99253 100644
--- a/artifacts/api-server/src/routes/index.ts
+++ b/artifacts/api-server/src/routes/index.ts
@@ -1,8 +1,10 @@
import { Router, type IRouter } from "express";
import healthRouter from "./health";
+import contactRouter from "./contact";
const router: IRouter = Router();
router.use(healthRouter);
+router.use(contactRouter);
export default router;
diff --git a/artifacts/joachim-portfolio/public/opengraph.jpg b/artifacts/joachim-portfolio/public/opengraph.jpg
index f8cc78a..2b0df9d 100644
Binary files a/artifacts/joachim-portfolio/public/opengraph.jpg and b/artifacts/joachim-portfolio/public/opengraph.jpg differ
diff --git a/artifacts/joachim-portfolio/src/components/contact.tsx b/artifacts/joachim-portfolio/src/components/contact.tsx
index a5359e7..5daa667 100644
--- a/artifacts/joachim-portfolio/src/components/contact.tsx
+++ b/artifacts/joachim-portfolio/src/components/contact.tsx
@@ -1,10 +1,10 @@
import { motion } from "framer-motion";
-import { Mail, ExternalLink, ArrowRight } from "lucide-react";
-
-const emailAddresses = [
- { address: "jh@unixweb.de", label: "jh@unixweb.de" },
- { address: "kontakt@joachimhummel.de", label: "kontakt@joachimhummel.de" },
-];
+import { ExternalLink, ArrowRight, Send, CheckCircle, AlertCircle, Loader2 } from "lucide-react";
+import { useState } from "react";
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { z } from "zod";
+import { useSendContactMessage } from "@workspace/api-client-react";
const links = [
{
@@ -19,6 +19,177 @@ const links = [
},
];
+const contactSchema = z.object({
+ name: z.string().min(1, "Bitte geben Sie Ihren Namen ein").max(100),
+ email: z.string().email("Bitte geben Sie eine gültige E-Mail-Adresse ein").max(200),
+ subject: z.string().max(200).optional(),
+ message: z.string().min(1, "Bitte geben Sie eine Nachricht ein").max(5000, "Die Nachricht ist zu lang (max. 5000 Zeichen)"),
+});
+
+type ContactFormData = z.infer;
+
+function ContactForm() {
+ const [status, setStatus] = useState<"idle" | "success" | "error">("idle");
+ const [serverMessage, setServerMessage] = useState("");
+
+ const {
+ register,
+ handleSubmit,
+ reset,
+ formState: { errors },
+ } = useForm({
+ resolver: zodResolver(contactSchema),
+ });
+
+ const { mutate: sendMessage, isPending } = useSendContactMessage({
+ mutation: {
+ onSuccess: (data) => {
+ setServerMessage(data.message);
+ setStatus("success");
+ reset();
+ },
+ onError: (err: unknown) => {
+ const apiErr = err as { data?: { message?: string } };
+ setServerMessage(
+ apiErr?.data?.message ??
+ "Die Nachricht konnte nicht gesendet werden. Bitte versuchen Sie es später erneut.",
+ );
+ setStatus("error");
+ },
+ },
+ });
+
+ const onSubmit = (data: ContactFormData) => {
+ setStatus("idle");
+ sendMessage({ data: { name: data.name, email: data.email, subject: data.subject, message: data.message } });
+ };
+
+ if (status === "success") {
+ return (
+
+
+
+
Nachricht gesendet!
+
{serverMessage}
+
+
+
+ );
+ }
+
+ return (
+
+ );
+}
+
export function Contact() {
return (
@@ -40,39 +211,6 @@ export function Contact() {
-
- {emailAddresses.map((email, i) => (
-
-
-
-
-
-
- {i === 0 ? "Hauptadresse" : "Alternativ"}
-
-
- {email.label}
-
-
-
-
- ))}
-
-
{links.map((link) => (
))}
+
+
+
Direkt schreiben
+
+
diff --git a/artifacts/joachim-portfolio/src/components/experience.tsx b/artifacts/joachim-portfolio/src/components/experience.tsx
index 820c8c1..9eaee04 100644
--- a/artifacts/joachim-portfolio/src/components/experience.tsx
+++ b/artifacts/joachim-portfolio/src/components/experience.tsx
@@ -23,7 +23,7 @@ const stations: Station[] = [
],
},
{
- period: "01/2024 – 05/2025",
+ period: "01/2024 – heute",
role: "Senior IT-Consultant",
client: "Landesamt für Statistik Bayern, München",
type: "behoerde",
diff --git a/artifacts/joachim-portfolio/src/components/navbar.tsx b/artifacts/joachim-portfolio/src/components/navbar.tsx
index e0e93a3..9da7697 100644
--- a/artifacts/joachim-portfolio/src/components/navbar.tsx
+++ b/artifacts/joachim-portfolio/src/components/navbar.tsx
@@ -1,6 +1,6 @@
import { useState } from "react";
import { motion, useScroll, useMotionValueEvent } from "framer-motion";
-import { Menu, X, ExternalLink } from "lucide-react";
+import { Menu, X } from "lucide-react";
export function Navbar() {
const [isScrolled, setIsScrolled] = useState(false);
@@ -12,13 +12,11 @@ export function Navbar() {
});
const navLinks = [
- { name: "Kompetenzen", href: "#competencies" },
{ name: "Projekte", href: "#projects" },
{ name: "Erfahrung", href: "#experience" },
+ { name: "Über mich", href: "#bio" },
];
- const blogLinks: { name: string; href: string }[] = [];
-
return (
))}
-
- {blogLinks.map((link) => (
-
- {link.name}
-
-
- ))}
@@ -104,19 +88,6 @@ export function Navbar() {
{link.name}
))}
-
- {blogLinks.map((link) => (
-
- {link.name}
-
-
- ))}
setMobileOpen(false)}
diff --git a/attached_assets/Bildschirmfoto_2026-05-15_um_17.56.16_1778861125632.png b/attached_assets/Bildschirmfoto_2026-05-15_um_17.56.16_1778861125632.png
new file mode 100644
index 0000000..b908f37
Binary files /dev/null and b/attached_assets/Bildschirmfoto_2026-05-15_um_17.56.16_1778861125632.png differ
diff --git a/lib/api-client-react/src/generated/api.schemas.ts b/lib/api-client-react/src/generated/api.schemas.ts
index 0439d7d..58a992f 100644
--- a/lib/api-client-react/src/generated/api.schemas.ts
+++ b/lib/api-client-react/src/generated/api.schemas.ts
@@ -1,10 +1,38 @@
/**
- * Generated by orval v8.5.3 🍺
+ * Generated by orval v8.9.1 🍺
* Do not edit manually.
* Api
* API specification
* OpenAPI spec version: 0.1.0
*/
+export interface ContactRequest {
+ /**
+ * @minLength 1
+ * @maxLength 100
+ */
+ name: string;
+ /** @maxLength 200 */
+ email: string;
+ /** @maxLength 200 */
+ subject?: string;
+ /**
+ * @minLength 1
+ * @maxLength 5000
+ */
+ message: string;
+}
+
+export interface ContactResponse {
+ success: boolean;
+ message: string;
+}
+
+export interface ContactError {
+ success: boolean;
+ message: string;
+}
+
export interface HealthStatus {
status: string;
}
+
diff --git a/lib/api-client-react/src/generated/api.ts b/lib/api-client-react/src/generated/api.ts
index ba51403..56e0922 100644
--- a/lib/api-client-react/src/generated/api.ts
+++ b/lib/api-client-react/src/generated/api.ts
@@ -1,101 +1,190 @@
/**
- * Generated by orval v8.5.3 🍺
+ * Generated by orval v8.9.1 🍺
* Do not edit manually.
* Api
* API specification
* OpenAPI spec version: 0.1.0
*/
-import { useQuery } from "@tanstack/react-query";
+import {
+ useMutation,
+ useQuery
+} from '@tanstack/react-query';
import type {
+ MutationFunction,
QueryFunction,
QueryKey,
+ UseMutationOptions,
+ UseMutationResult,
UseQueryOptions,
- UseQueryResult,
-} from "@tanstack/react-query";
+ UseQueryResult
+} from '@tanstack/react-query';
-import type { HealthStatus } from "./api.schemas";
+import type {
+ ContactError,
+ ContactRequest,
+ ContactResponse,
+ HealthStatus
+} from './api.schemas';
-import { customFetch } from "../custom-fetch";
-import type { ErrorType } from "../custom-fetch";
+import { customFetch } from '../custom-fetch';
+import type { ErrorType , BodyType } from '../custom-fetch';
type AwaitedInput = PromiseLike | T;
-type Awaited = O extends AwaitedInput ? T : never;
+ type Awaited = O extends AwaitedInput ? T : never;
+
type SecondParameter unknown> = Parameters[1];
+
+
+export const getSendContactMessageUrl = () => {
+
+
+
+
+ return `/api/contact`
+}
+
+/**
+ * Sends a contact message via email
+ * @summary Send contact form message
+ */
+export const sendContactMessage = async (contactRequest: ContactRequest, options?: RequestInit): Promise => {
+
+ return customFetch(getSendContactMessageUrl(),
+ {
+ ...options,
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json', ...options?.headers },
+ body: JSON.stringify(
+ contactRequest,)
+ }
+);}
+
+
+
+
+export const getSendContactMessageMutationOptions = ,
+ TContext = unknown>(options?: { mutation?:UseMutationOptions>, TError,{data: BodyType}, TContext>, request?: SecondParameter}
+): UseMutationOptions>, TError,{data: BodyType}, TContext> => {
+
+const mutationKey = ['sendContactMessage'];
+const {mutation: mutationOptions, request: requestOptions} = options ?
+ options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
+ options
+ : {...options, mutation: {...options.mutation, mutationKey}}
+ : {mutation: { mutationKey, }, request: undefined};
+
+
+
+
+ const mutationFn: MutationFunction>, {data: BodyType}> = (props) => {
+ const {data} = props ?? {};
+
+ return sendContactMessage(data,requestOptions)
+ }
+
+
+
+
+
+
+ return { mutationFn, ...mutationOptions }}
+
+ export type SendContactMessageMutationResult = NonNullable>>
+ export type SendContactMessageMutationBody = BodyType
+ export type SendContactMessageMutationError = ErrorType
+
+ /**
+ * @summary Send contact form message
+ */
+export const useSendContactMessage = ,
+ TContext = unknown>(options?: { mutation?:UseMutationOptions>, TError,{data: BodyType}, TContext>, request?: SecondParameter}
+ ): UseMutationResult<
+ Awaited>,
+ TError,
+ {data: BodyType},
+ TContext
+ > => {
+ return useMutation(getSendContactMessageMutationOptions(options));
+ }
+
+export const getHealthCheckUrl = () => {
+
+
+
+
+ return `/api/healthz`
+}
+
/**
* Returns server health status
* @summary Health check
*/
-export const getHealthCheckUrl = () => {
- return `/api/healthz`;
-};
+export const healthCheck = async ( options?: RequestInit): Promise => {
-export const healthCheck = async (
- options?: RequestInit,
-): Promise => {
- return customFetch(getHealthCheckUrl(), {
+ return customFetch(getHealthCheckUrl(),
+ {
...options,
- method: "GET",
- });
-};
+ method: 'GET'
+
+
+ }
+);}
+
+
+
+
export const getHealthCheckQueryKey = () => {
- return [`/api/healthz`] as const;
-};
+ return [
+ `/api/healthz`
+ ] as const;
+ }
-export const getHealthCheckQueryOptions = <
- TData = Awaited>,
- TError = ErrorType,
->(options?: {
- query?: UseQueryOptions<
- Awaited>,
- TError,
- TData
- >;
- request?: SecondParameter;
-}) => {
- const { query: queryOptions, request: requestOptions } = options ?? {};
- const queryKey = queryOptions?.queryKey ?? getHealthCheckQueryKey();
+export const getHealthCheckQueryOptions = >, TError = ErrorType>( options?: { query?:UseQueryOptions>, TError, TData>, request?: SecondParameter}
+) => {
- const queryFn: QueryFunction>> = ({
- signal,
- }) => healthCheck({ signal, ...requestOptions });
+const {query: queryOptions, request: requestOptions} = options ?? {};
- return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
- Awaited>,
- TError,
- TData
- > & { queryKey: QueryKey };
-};
+ const queryKey = queryOptions?.queryKey ?? getHealthCheckQueryKey();
+
+
+
+ const queryFn: QueryFunction>> = ({ signal }) => healthCheck({ signal, ...requestOptions });
+
+
+
+
+
+ return { queryKey, queryFn, ...queryOptions} as UseQueryOptions>, TError, TData> & { queryKey: QueryKey }
+}
+
+export type HealthCheckQueryResult = NonNullable>>
+export type HealthCheckQueryError = ErrorType
-export type HealthCheckQueryResult = NonNullable<
- Awaited>
->;
-export type HealthCheckQueryError = ErrorType;
/**
* @summary Health check
*/
-export function useHealthCheck<
- TData = Awaited>,
- TError = ErrorType,
->(options?: {
- query?: UseQueryOptions<
- Awaited>,
- TError,
- TData
- >;
- request?: SecondParameter;
-}): UseQueryResult & { queryKey: QueryKey } {
- const queryOptions = getHealthCheckQueryOptions(options);
+export function useHealthCheck>, TError = ErrorType>(
+ options?: { query?:UseQueryOptions>, TError, TData>, request?: SecondParameter}
- const query = useQuery(queryOptions) as UseQueryResult & {
- queryKey: QueryKey;
- };
+ ): UseQueryResult & { queryKey: QueryKey } {
+
+ const queryOptions = getHealthCheckQueryOptions(options)
+
+ const query = useQuery(queryOptions) as UseQueryResult & { queryKey: QueryKey };
return { ...query, queryKey: queryOptions.queryKey };
}
+
+
+
+
+
+
+
diff --git a/lib/api-spec/openapi.yaml b/lib/api-spec/openapi.yaml
index bbcb269..c9a5da9 100644
--- a/lib/api-spec/openapi.yaml
+++ b/lib/api-spec/openapi.yaml
@@ -10,7 +10,40 @@ servers:
tags:
- name: health
description: Health operations
+ - name: contact
+ description: Contact form
paths:
+ /contact:
+ post:
+ operationId: sendContactMessage
+ tags: [contact]
+ summary: Send contact form message
+ description: Sends a contact message via email
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ContactRequest"
+ responses:
+ "200":
+ description: Message sent successfully
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ContactResponse"
+ "400":
+ description: Validation error
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ContactError"
+ "500":
+ description: Server error
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ContactError"
/healthz:
get:
operationId: healthCheck
@@ -26,6 +59,48 @@ paths:
$ref: "#/components/schemas/HealthStatus"
components:
schemas:
+ ContactRequest:
+ type: object
+ properties:
+ name:
+ type: string
+ minLength: 1
+ maxLength: 100
+ email:
+ type: string
+ format: email
+ maxLength: 200
+ subject:
+ type: string
+ maxLength: 200
+ message:
+ type: string
+ minLength: 1
+ maxLength: 5000
+ required:
+ - name
+ - email
+ - message
+ ContactResponse:
+ type: object
+ properties:
+ success:
+ type: boolean
+ message:
+ type: string
+ required:
+ - success
+ - message
+ ContactError:
+ type: object
+ properties:
+ success:
+ type: boolean
+ message:
+ type: string
+ required:
+ - success
+ - message
HealthStatus:
type: object
properties:
diff --git a/lib/api-zod/src/generated/api.ts b/lib/api-zod/src/generated/api.ts
index fee3c58..3db8cc5 100644
--- a/lib/api-zod/src/generated/api.ts
+++ b/lib/api-zod/src/generated/api.ts
@@ -1,16 +1,46 @@
/**
- * Generated by orval v8.5.3 🍺
+ * Generated by orval v8.9.1 🍺
* Do not edit manually.
* Api
* API specification
* OpenAPI spec version: 0.1.0
*/
-import * as zod from "zod";
+import * as zod from 'zod';
+
+
+/**
+ * Sends a contact message via email
+ * @summary Send contact form message
+ */
+export const sendContactMessageBodyNameMax = 100;
+
+export const sendContactMessageBodyEmailMax = 200;
+
+export const sendContactMessageBodySubjectMax = 200;
+
+export const sendContactMessageBodyMessageMax = 5000;
+
+
+
+export const SendContactMessageBody = zod.object({
+ "name": zod.string().min(1).max(sendContactMessageBodyNameMax),
+ "email": zod.string().email().max(sendContactMessageBodyEmailMax),
+ "subject": zod.string().max(sendContactMessageBodySubjectMax).optional(),
+ "message": zod.string().min(1).max(sendContactMessageBodyMessageMax)
+})
+
+export const SendContactMessageResponse = zod.object({
+ "success": zod.boolean(),
+ "message": zod.string()
+})
+
/**
* Returns server health status
* @summary Health check
*/
export const HealthCheckResponse = zod.object({
- status: zod.string(),
-});
+ "status": zod.string()
+})
+
+
diff --git a/lib/api-zod/src/generated/types/contactError.ts b/lib/api-zod/src/generated/types/contactError.ts
new file mode 100644
index 0000000..bf3a22e
--- /dev/null
+++ b/lib/api-zod/src/generated/types/contactError.ts
@@ -0,0 +1,12 @@
+/**
+ * Generated by orval v8.9.1 🍺
+ * Do not edit manually.
+ * Api
+ * API specification
+ * OpenAPI spec version: 0.1.0
+ */
+
+export interface ContactError {
+ success: boolean;
+ message: string;
+}
diff --git a/lib/api-zod/src/generated/types/contactRequest.ts b/lib/api-zod/src/generated/types/contactRequest.ts
new file mode 100644
index 0000000..1d6c42e
--- /dev/null
+++ b/lib/api-zod/src/generated/types/contactRequest.ts
@@ -0,0 +1,24 @@
+/**
+ * Generated by orval v8.9.1 🍺
+ * Do not edit manually.
+ * Api
+ * API specification
+ * OpenAPI spec version: 0.1.0
+ */
+
+export interface ContactRequest {
+ /**
+ * @minLength 1
+ * @maxLength 100
+ */
+ name: string;
+ /** @maxLength 200 */
+ email: string;
+ /** @maxLength 200 */
+ subject?: string;
+ /**
+ * @minLength 1
+ * @maxLength 5000
+ */
+ message: string;
+}
diff --git a/lib/api-zod/src/generated/types/contactResponse.ts b/lib/api-zod/src/generated/types/contactResponse.ts
new file mode 100644
index 0000000..53ae2ba
--- /dev/null
+++ b/lib/api-zod/src/generated/types/contactResponse.ts
@@ -0,0 +1,12 @@
+/**
+ * Generated by orval v8.9.1 🍺
+ * Do not edit manually.
+ * Api
+ * API specification
+ * OpenAPI spec version: 0.1.0
+ */
+
+export interface ContactResponse {
+ success: boolean;
+ message: string;
+}
diff --git a/lib/api-zod/src/generated/types/healthStatus.ts b/lib/api-zod/src/generated/types/healthStatus.ts
index f1ad88c..eba6b18 100644
--- a/lib/api-zod/src/generated/types/healthStatus.ts
+++ b/lib/api-zod/src/generated/types/healthStatus.ts
@@ -1,5 +1,5 @@
/**
- * Generated by orval v8.5.3 🍺
+ * Generated by orval v8.9.1 🍺
* Do not edit manually.
* Api
* API specification
diff --git a/lib/api-zod/src/generated/types/index.ts b/lib/api-zod/src/generated/types/index.ts
index c816559..6559778 100644
--- a/lib/api-zod/src/generated/types/index.ts
+++ b/lib/api-zod/src/generated/types/index.ts
@@ -1,9 +1,12 @@
/**
- * Generated by orval v8.5.3 🍺
+ * Generated by orval v8.9.1 🍺
* Do not edit manually.
* Api
* API specification
* OpenAPI spec version: 0.1.0
*/
-export * from "./healthStatus";
+export * from './contactError';
+export * from './contactRequest';
+export * from './contactResponse';
+export * from './healthStatus';
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 595b6d0..e37fb85 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -166,6 +166,9 @@ importers:
artifacts/api-server:
dependencies:
+ '@getbrevo/brevo':
+ specifier: ^5.0.4
+ version: 5.0.4
'@workspace/api-zod':
specifier: workspace:*
version: link:../../lib/api-zod
@@ -767,6 +770,10 @@ packages:
'@gerrit0/mini-shiki@3.23.0':
resolution: {integrity: sha512-bEMORlG0cqdjVyCEuU0cDQbORWX+kYCeo0kV1lbxF5bt4r7SID2l9bqsxJEM0zndaxpOUT7riCyIVEuqq/Ynxg==}
+ '@getbrevo/brevo@5.0.4':
+ resolution: {integrity: sha512-wN4mHE6O0Pb/d/Dh3E4MAm2zCHbLLUcFF8/wgwTeMPy9KzHBYLu7S/nsJbzd0AWL09MHn8vwue8ULL9YOzluOQ==}
+ engines: {node: '>=18.0.0'}
+
'@hookform/resolvers@3.10.0':
resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==}
peerDependencies:
@@ -3317,6 +3324,8 @@ snapshots:
'@shikijs/types': 3.23.0
'@shikijs/vscode-textmate': 10.0.2
+ '@getbrevo/brevo@5.0.4': {}
+
'@hookform/resolvers@3.10.0(react-hook-form@7.75.0(react@19.1.0))':
dependencies:
react-hook-form: 7.75.0(react@19.1.0)