feature: contact form

This commit is contained in:
Benno Tielen 2025-02-02 11:30:07 +01:00
parent ea81277e5c
commit da82f554c9
6 changed files with 310 additions and 8 deletions

View file

@ -1,3 +1,4 @@
DATABASE_URI=mongodb://127.0.0.1/dreikoenige
PAYLOAD_SECRET=YOUR_SECRET_HERE
GOOGLE_BUCKET=google_storage_bucket
GOOGLE_BUCKET=google_storage_bucket
RESEND_API_KEY=some_api_key

View file

@ -32,7 +32,9 @@
"qs-esm": "^7.0.2",
"react": "19.0.0-rc-65a56d0e-20241020",
"react-dom": "19.0.0-rc-65a56d0e-20241020",
"sharp": "0.32.6"
"resend": "^4.1.1",
"sharp": "0.32.6",
"zod": "^3.24.1"
},
"devDependencies": {
"@chromatic-com/storybook": "^1.6.1",

50
src/app/actions.ts Normal file
View file

@ -0,0 +1,50 @@
import { z } from 'zod'
import { Resend } from 'resend'
/**
* Send email through Resend API
*
* @param prevState
* @param formData
*/
export async function send(prevState: any, formData: FormData) {
const schema = z.object({
email: z.string().email(),
subject: z.string(),
name: z.string(),
message: z.string()
});
const validatedFields = schema.safeParse({
name: formData.get("name"),
email: formData.get("email"),
subject: formData.get("subject"),
message: formData.get("message"),
})
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
message: "Nicht alle Felder waren gültig"
}
}
try {
const resend = new Resend(process.env.RESEND_API_KEY);
await resend.emails.send({
from: 'kontakt@resend.dev',
replyTo: validatedFields.data.email,
to: 'buero@sankt-clara.de',
subject: `Über Kontaktformular: ${validatedFields.data.subject}`,
text: `Nachricht von ${validatedFields.data.name}: \n\n ${validatedFields.data.message}`
});
} catch (e) {
return { message: "Fehler beim senden der Nachricht." };
}
return {
message: "Ihre Nachricht wurde erfolgreich übermittelt"
}
}

View file

@ -8,6 +8,7 @@ type ButtonProps = {
href?: string,
target?: "_blank" | "_self"
children: React.ReactNode,
disabled?: boolean,
onClick?: () => void,
}
@ -19,6 +20,7 @@ export function Button(
children,
onClick,
href,
disabled = false,
target = "_self"
}: ButtonProps
) {
@ -47,6 +49,7 @@ export function Button(
type={type}
onClick={onClick}
className={style}
disabled={disabled}
>
{children}
</button>

View file

@ -1,15 +1,26 @@
'use client'
import { Input } from '@/components/Input/Input'
import { Button } from '@/components/Button/Button'
import styles from "./styles.module.scss"
import classNames from 'classnames'
import { send } from '@/app/actions'
import { useActionState } from 'react'
type ContactFormProps = {
schema?: "base" | "contrast"
}
const initialState = {
message: '',
}
export const ContactForm = ({schema}: ContactFormProps) => {
const [state, formAction, pending] = useActionState(send, initialState)
return (
<form>
<form action={formAction}>
<div className={classNames(styles.row, styles.firsRow)}>
<Input name={"name"} type={"text"} placeholder={"Name"} />
<Input name={"email"} type={"email"} placeholder={"E-Mail Adresse"} />
@ -20,8 +31,11 @@ export const ContactForm = ({schema}: ContactFormProps) => {
<div className={styles.row}>
<Input name={"message"} type={"textarea"} placeholder={"Ihre Nachricht"} />
</div>
<p>
{state.message}
</p>
<div className={styles.row}>
<Button size={"lg"} type={"submit"} schema={schema}>Abschicken</Button>
<Button size={"lg"} type={"submit"} schema={schema} disabled={pending}>Abschicken</Button>
</div>
</form>
)

240
yarn.lock
View file

@ -3376,6 +3376,13 @@ __metadata:
languageName: node
linkType: hard
"@one-ini/wasm@npm:0.1.1":
version: 0.1.1
resolution: "@one-ini/wasm@npm:0.1.1"
checksum: 10c0/54700e055037f1a63bfcc86d24822203b25759598c2c3e295d1435130a449108aebc119c9c2e467744767dbe0b6ab47a182c61aa1071ba7368f5e20ab197ba65
languageName: node
linkType: hard
"@payloadcms/db-postgres@npm:^3.3.0":
version: 3.3.0
resolution: "@payloadcms/db-postgres@npm:3.3.0"
@ -3632,6 +3639,20 @@ __metadata:
languageName: node
linkType: hard
"@react-email/render@npm:1.0.1":
version: 1.0.1
resolution: "@react-email/render@npm:1.0.1"
dependencies:
html-to-text: "npm:9.0.5"
js-beautify: "npm:^1.14.11"
react-promise-suspense: "npm:0.3.4"
peerDependencies:
react: ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^18.0 || ^19.0 || ^19.0.0-rc
checksum: 10c0/52a96fb4ca502cb577071779a665520403b1f5910b7267df91486d2a05bda7ede9e65e2427752c870801aae5c9d18a2d9da0706032388a966ef910ce831a5af5
languageName: node
linkType: hard
"@rtsao/scc@npm:^1.1.0":
version: 1.1.0
resolution: "@rtsao/scc@npm:1.1.0"
@ -3646,6 +3667,16 @@ __metadata:
languageName: node
linkType: hard
"@selderee/plugin-htmlparser2@npm:^0.11.0":
version: 0.11.0
resolution: "@selderee/plugin-htmlparser2@npm:0.11.0"
dependencies:
domhandler: "npm:^5.0.3"
selderee: "npm:^0.11.0"
checksum: 10c0/e938ba9aeb31a9cf30dcb2977ef41685c598bf744bedc88c57aa9e8b7e71b51781695cf99c08aac50773fd7714eba670bd2a079e46db0788abe40c6d220084eb
languageName: node
linkType: hard
"@sinclair/typebox@npm:^0.27.8":
version: 0.27.8
resolution: "@sinclair/typebox@npm:0.27.8"
@ -6807,6 +6838,13 @@ __metadata:
languageName: node
linkType: hard
"commander@npm:^10.0.0":
version: 10.0.1
resolution: "commander@npm:10.0.1"
checksum: 10c0/53f33d8927758a911094adadda4b2cbac111a5b377d8706700587650fd8f45b0bbe336de4b5c3fe47fd61f420a3d9bd452b6e0e6e5600a7e74d7bf0174f6efe3
languageName: node
linkType: hard
"commander@npm:^2.20.0, commander@npm:^2.20.3":
version: 2.20.3
resolution: "commander@npm:2.20.3"
@ -6856,6 +6894,16 @@ __metadata:
languageName: node
linkType: hard
"config-chain@npm:^1.1.13":
version: 1.1.13
resolution: "config-chain@npm:1.1.13"
dependencies:
ini: "npm:^1.3.4"
proto-list: "npm:~1.2.1"
checksum: 10c0/39d1df18739d7088736cc75695e98d7087aea43646351b028dfabd5508d79cf6ef4c5bcd90471f52cd87ae470d1c5490c0a8c1a292fbe6ee9ff688061ea0963e
languageName: node
linkType: hard
"consola@npm:^3.2.3":
version: 3.2.3
resolution: "consola@npm:3.2.3"
@ -7361,7 +7409,7 @@ __metadata:
languageName: node
linkType: hard
"deepmerge@npm:4.3.1, deepmerge@npm:^4.2.2":
"deepmerge@npm:4.3.1, deepmerge@npm:^4.2.2, deepmerge@npm:^4.3.1":
version: 4.3.1
resolution: "deepmerge@npm:4.3.1"
checksum: 10c0/e53481aaf1aa2c4082b5342be6b6d8ad9dfe387bc92ce197a66dea08bd4265904a087e75e464f14d1347cf2ac8afe1e4c16b266e0561cc5df29382d3c5f80044
@ -7577,6 +7625,17 @@ __metadata:
languageName: node
linkType: hard
"dom-serializer@npm:^2.0.0":
version: 2.0.0
resolution: "dom-serializer@npm:2.0.0"
dependencies:
domelementtype: "npm:^2.3.0"
domhandler: "npm:^5.0.2"
entities: "npm:^4.2.0"
checksum: 10c0/d5ae2b7110ca3746b3643d3ef60ef823f5f078667baf530cec096433f1627ec4b6fa8c072f09d079d7cda915fd2c7bc1b7b935681e9b09e591e1e15f4040b8e2
languageName: node
linkType: hard
"domain-browser@npm:^4.22.0":
version: 4.23.0
resolution: "domain-browser@npm:4.23.0"
@ -7584,7 +7643,7 @@ __metadata:
languageName: node
linkType: hard
"domelementtype@npm:^2.0.1, domelementtype@npm:^2.2.0":
"domelementtype@npm:^2.0.1, domelementtype@npm:^2.2.0, domelementtype@npm:^2.3.0":
version: 2.3.0
resolution: "domelementtype@npm:2.3.0"
checksum: 10c0/686f5a9ef0fff078c1412c05db73a0dce096190036f33e400a07e2a4518e9f56b1e324f5c576a0a747ef0e75b5d985c040b0d51945ce780c0dd3c625a18cd8c9
@ -7600,6 +7659,15 @@ __metadata:
languageName: node
linkType: hard
"domhandler@npm:^5.0.2, domhandler@npm:^5.0.3":
version: 5.0.3
resolution: "domhandler@npm:5.0.3"
dependencies:
domelementtype: "npm:^2.3.0"
checksum: 10c0/bba1e5932b3e196ad6862286d76adc89a0dbf0c773e5ced1eb01f9af930c50093a084eff14b8de5ea60b895c56a04d5de8bbc4930c5543d029091916770b2d2a
languageName: node
linkType: hard
"domutils@npm:^2.5.2, domutils@npm:^2.8.0":
version: 2.8.0
resolution: "domutils@npm:2.8.0"
@ -7611,6 +7679,17 @@ __metadata:
languageName: node
linkType: hard
"domutils@npm:^3.0.1":
version: 3.2.2
resolution: "domutils@npm:3.2.2"
dependencies:
dom-serializer: "npm:^2.0.0"
domelementtype: "npm:^2.3.0"
domhandler: "npm:^5.0.3"
checksum: 10c0/47938f473b987ea71cd59e59626eb8666d3aa8feba5266e45527f3b636c7883cca7e582d901531961f742c519d7514636b7973353b648762b2e3bedbf235fada
languageName: node
linkType: hard
"dot-case@npm:^3.0.4":
version: 3.0.4
resolution: "dot-case@npm:3.0.4"
@ -7658,9 +7737,11 @@ __metadata:
qs-esm: "npm:^7.0.2"
react: "npm:19.0.0-rc-65a56d0e-20241020"
react-dom: "npm:19.0.0-rc-65a56d0e-20241020"
resend: "npm:^4.1.1"
sharp: "npm:0.32.6"
storybook: "npm:^8.2.9"
typescript: "npm:5.5.4"
zod: "npm:^3.24.1"
languageName: unknown
linkType: soft
@ -7808,6 +7889,20 @@ __metadata:
languageName: node
linkType: hard
"editorconfig@npm:^1.0.4":
version: 1.0.4
resolution: "editorconfig@npm:1.0.4"
dependencies:
"@one-ini/wasm": "npm:0.1.1"
commander: "npm:^10.0.0"
minimatch: "npm:9.0.1"
semver: "npm:^7.5.3"
bin:
editorconfig: bin/editorconfig
checksum: 10c0/ed6985959d7b34a56e1c09bef118758c81c969489b768d152c93689fce8403b0452462e934f665febaba3478eebc0fd41c0a36100783eaadf6d926c4abc87a3d
languageName: node
linkType: hard
"ee-first@npm:1.1.1":
version: 1.1.1
resolution: "ee-first@npm:1.1.1"
@ -7911,6 +8006,13 @@ __metadata:
languageName: node
linkType: hard
"entities@npm:^4.2.0, entities@npm:^4.4.0":
version: 4.5.0
resolution: "entities@npm:4.5.0"
checksum: 10c0/5b039739f7621f5d1ad996715e53d964035f75ad3b9a4d38c6b3804bb226e282ffeae2443624d8fdd9c47d8e926ae9ac009c54671243f0c3294c26af7cc85250
languageName: node
linkType: hard
"env-paths@npm:^2.2.0, env-paths@npm:^2.2.1":
version: 2.2.1
resolution: "env-paths@npm:2.2.1"
@ -9026,6 +9128,13 @@ __metadata:
languageName: node
linkType: hard
"fast-deep-equal@npm:^2.0.1":
version: 2.0.1
resolution: "fast-deep-equal@npm:2.0.1"
checksum: 10c0/1602e0d6ed63493c865cc6b03f9070d6d3926e8cd086a123060b58f80a295f3f08b1ecfb479ae7c45b7fd45535202aea7cf5b49bc31bffb81c20b1502300be84
languageName: node
linkType: hard
"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3":
version: 3.1.3
resolution: "fast-deep-equal@npm:3.1.3"
@ -9740,7 +9849,7 @@ __metadata:
languageName: node
linkType: hard
"glob@npm:^10.2.2, glob@npm:^10.3.10":
"glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.3.3":
version: 10.4.5
resolution: "glob@npm:10.4.5"
dependencies:
@ -10153,6 +10262,19 @@ __metadata:
languageName: node
linkType: hard
"html-to-text@npm:9.0.5":
version: 9.0.5
resolution: "html-to-text@npm:9.0.5"
dependencies:
"@selderee/plugin-htmlparser2": "npm:^0.11.0"
deepmerge: "npm:^4.3.1"
dom-serializer: "npm:^2.0.0"
htmlparser2: "npm:^8.0.2"
selderee: "npm:^0.11.0"
checksum: 10c0/5d2c77b798cf88a81b1da2fc1ea1a3b3e2ff49fe5a3d812392f802fff18ec315cf0969bd7846ef2eb7df8c37f463bc63e8cbdcf84e42696c6f3e15dfa61cdf4f
languageName: node
linkType: hard
"html-webpack-plugin@npm:^5.5.0":
version: 5.6.0
resolution: "html-webpack-plugin@npm:5.6.0"
@ -10186,6 +10308,18 @@ __metadata:
languageName: node
linkType: hard
"htmlparser2@npm:^8.0.2":
version: 8.0.2
resolution: "htmlparser2@npm:8.0.2"
dependencies:
domelementtype: "npm:^2.3.0"
domhandler: "npm:^5.0.3"
domutils: "npm:^3.0.1"
entities: "npm:^4.4.0"
checksum: 10c0/609cca85886d0bf2c9a5db8c6926a89f3764596877492e2caa7a25a789af4065bc6ee2cdc81807fe6b1d03a87bf8a373b5a754528a4cc05146b713c20575aab4
languageName: node
linkType: hard
"http-cache-semantics@npm:^4.0.0, http-cache-semantics@npm:^4.1.1":
version: 4.1.1
resolution: "http-cache-semantics@npm:4.1.1"
@ -10913,6 +11047,30 @@ __metadata:
languageName: node
linkType: hard
"js-beautify@npm:^1.14.11":
version: 1.15.1
resolution: "js-beautify@npm:1.15.1"
dependencies:
config-chain: "npm:^1.1.13"
editorconfig: "npm:^1.0.4"
glob: "npm:^10.3.3"
js-cookie: "npm:^3.0.5"
nopt: "npm:^7.2.0"
bin:
css-beautify: js/bin/css-beautify.js
html-beautify: js/bin/html-beautify.js
js-beautify: js/bin/js-beautify.js
checksum: 10c0/4140dd95537143eb429b6c8e47e21310f16c032d97a03163c6c7c0502bc663242a5db08d3ad941b87f24a142ce4f9190c556d2340bcd056545326377dfae5362
languageName: node
linkType: hard
"js-cookie@npm:^3.0.5":
version: 3.0.5
resolution: "js-cookie@npm:3.0.5"
checksum: 10c0/04a0e560407b4489daac3a63e231d35f4e86f78bff9d792011391b49c59f721b513411cd75714c418049c8dc9750b20fcddad1ca5a2ca616c3aca4874cce5b3a
languageName: node
linkType: hard
"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0":
version: 4.0.0
resolution: "js-tokens@npm:4.0.0"
@ -11173,6 +11331,13 @@ __metadata:
languageName: node
linkType: hard
"leac@npm:^0.6.0":
version: 0.6.0
resolution: "leac@npm:0.6.0"
checksum: 10c0/5257781e10791ef8462eb1cbe5e48e3cda7692486f2a775265d6f5216cc088960c62f138163b8df0dcf2119d18673bfe7b050d6b41543d92a7b7ac90e4eb1e8b
languageName: node
linkType: hard
"leven@npm:^3.1.0":
version: 3.1.0
resolution: "leven@npm:3.1.0"
@ -12039,6 +12204,15 @@ __metadata:
languageName: node
linkType: hard
"minimatch@npm:9.0.1":
version: 9.0.1
resolution: "minimatch@npm:9.0.1"
dependencies:
brace-expansion: "npm:^2.0.1"
checksum: 10c0/aa043eb8822210b39888a5d0d28df0017b365af5add9bd522f180d2a6962de1cbbf1bdeacdb1b17f410dc3336bc8d76fb1d3e814cdc65d00c2f68e01f0010096
languageName: node
linkType: hard
"minimatch@npm:^3.0.2, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2":
version: 3.1.2
resolution: "minimatch@npm:3.1.2"
@ -12467,7 +12641,7 @@ __metadata:
languageName: node
linkType: hard
"nopt@npm:^7.0.0":
"nopt@npm:^7.0.0, nopt@npm:^7.2.0":
version: 7.2.1
resolution: "nopt@npm:7.2.1"
dependencies:
@ -12932,6 +13106,16 @@ __metadata:
languageName: node
linkType: hard
"parseley@npm:^0.12.0":
version: 0.12.1
resolution: "parseley@npm:0.12.1"
dependencies:
leac: "npm:^0.6.0"
peberminta: "npm:^0.9.0"
checksum: 10c0/df3de74172b72305b867298a71e5882c413df75d30f2bafb5fb70779dfd349c5e4db03441fbf8ca83da8e4aa72bd0ef2b5c73086c4825d27d1c649d61bc0bcc0
languageName: node
linkType: hard
"parseurl@npm:~1.3.3":
version: 1.3.3
resolution: "parseurl@npm:1.3.3"
@ -13128,6 +13312,13 @@ __metadata:
languageName: node
linkType: hard
"peberminta@npm:^0.9.0":
version: 0.9.0
resolution: "peberminta@npm:0.9.0"
checksum: 10c0/59c2c39269d9f7f559cf44582f1c0503524c6a9bc3478e0309adba2b41c71ab98745a239a4e6f98f46105291256e6d8f12ae9860d9f016b1c9a6f52c0b63bfe7
languageName: node
linkType: hard
"peek-readable@npm:^5.1.3":
version: 5.2.0
resolution: "peek-readable@npm:5.2.0"
@ -13744,6 +13935,13 @@ __metadata:
languageName: node
linkType: hard
"proto-list@npm:~1.2.1":
version: 1.2.4
resolution: "proto-list@npm:1.2.4"
checksum: 10c0/b9179f99394ec8a68b8afc817690185f3b03933f7b46ce2e22c1930dc84b60d09f5ad222beab4e59e58c6c039c7f7fcf620397235ef441a356f31f9744010e12
languageName: node
linkType: hard
"protocol-buffers-schema@npm:^3.3.1":
version: 3.6.0
resolution: "protocol-buffers-schema@npm:3.6.0"
@ -14109,6 +14307,15 @@ __metadata:
languageName: node
linkType: hard
"react-promise-suspense@npm:0.3.4":
version: 0.3.4
resolution: "react-promise-suspense@npm:0.3.4"
dependencies:
fast-deep-equal: "npm:^2.0.1"
checksum: 10c0/ab7a22f5400f9e9933995537bf6430a4c79e33a121aedb51864968e7604e5c40421fd539ead62554f32300b7d49755c79636de06caa36fe52973b626b4ddfebf
languageName: node
linkType: hard
"react-refresh@npm:^0.14.0":
version: 0.14.2
resolution: "react-refresh@npm:0.14.2"
@ -14406,6 +14613,15 @@ __metadata:
languageName: node
linkType: hard
"resend@npm:^4.1.1":
version: 4.1.1
resolution: "resend@npm:4.1.1"
dependencies:
"@react-email/render": "npm:1.0.1"
checksum: 10c0/37389232fd1b685ef63a10df3cded2f32559d98391b4d0e168271d1d68c8c3e5cdf6c9d7142a477fb814e700ca4e7b7b98d14dbacf84f915f6cd60d7eaf8621b
languageName: node
linkType: hard
"resolve-alpn@npm:^1.0.0":
version: 1.2.1
resolution: "resolve-alpn@npm:1.2.1"
@ -14768,6 +14984,15 @@ __metadata:
languageName: node
linkType: hard
"selderee@npm:^0.11.0":
version: 0.11.0
resolution: "selderee@npm:0.11.0"
dependencies:
parseley: "npm:^0.12.0"
checksum: 10c0/c2ad8313a0dbf3c0b74752a8d03cfbc0931ae77a36679cdb64733eb732c1762f95a5174249bf7e8b8103874cb0e013a030f9c8b72f5d41e62f1d847d4a845d39
languageName: node
linkType: hard
"semver-regex@npm:^4.0.5":
version: 4.0.5
resolution: "semver-regex@npm:4.0.5"
@ -16989,6 +17214,13 @@ __metadata:
languageName: node
linkType: hard
"zod@npm:^3.24.1":
version: 3.24.1
resolution: "zod@npm:3.24.1"
checksum: 10c0/0223d21dbaa15d8928fe0da3b54696391d8e3e1e2d0283a1a070b5980a1dbba945ce631c2d1eccc088fdbad0f2dfa40155590bf83732d3ac4fcca2cc9237591b
languageName: node
linkType: hard
"zwitch@npm:^2.0.0":
version: 2.0.4
resolution: "zwitch@npm:2.0.4"