Jak stworzyc blok formularza kontaktowego
Zbuduj headless formularz kontaktowy: stworz go w Form Builderze Cmssy, a nastepnie wyrenderuj we wlasnej stronie Next.js przez SDK.
Co budujemy
Formularz kontaktowy dla strony headless w Cmssy Formularz tworzysz w Form Builderze Cmssy (panel na cmssy.io), a renderujesz go we wlasnej aplikacji Next.js (App Router), ktora wdrazasz samodzielnie (Vercel itp.). Wizualny edytor Cmssy osadza Twoja wdrozona strone, aby redaktorzy mogli edytowac tresc na zywo. Ten setup zawiera:
- Pola imie, email i wiadomosc z walidacja - zdefiniowane raz w Form Builderze
- Walidacje po stronie serwera, powiadomienia email i webhooki obslugiwane przez Cmssy
- Zgloszenia zapisywane w Cmssy i widoczne w panelu
- Stany sukcesu/bledu z animacjami, renderowane w Twojej aplikacji
- Responsywny design z Tailwind CSS
Wymagania
- Node.js 18+ zainstalowany
- Aplikacja Next.js (App Router) z SDK:
npm install @cmssy/next @cmssy/react - Workspace Cmssy z tokenem API
Krok 1: Stworz formularz w Cmssy
W panelu Cmssy przejdz do Dashboard → Forms → Create Form. Nadaj nazwe i slug (np. contact-form), a nastepnie dodaj pola:
- name - text, wymagane
- email - email, wymagane
- message - textarea, wymagane, min. dlugosc 10
W Ustawieniach wybierz typ akcji contact, dodaj odbiorcow email i ustaw tekst przycisku, wiadomosc sukcesu oraz wiadomosc bledu. Etykiety i komunikaty sa wielojezyczne, wiec en i pl tworzysz w jednym miejscu. Opublikuj formularz, aby przyjmowal zgloszenia.
Krok 2: Zdefiniuj blok
W aplikacji Next.js stworz blocks/contact-form/block.ts. Schemat definiuje pola, ktore redaktorzy widza w edytorze Cmssy - tutaj pole typu form pozwala wybrac, ktory formularz renderowac:
import { defineBlock, fields } from "@cmssy/react";
import Component from "./src";
export const contactFormBlock = defineBlock({
type: "contact-form", // unikalny typ bloku, zapisywany na kazdej instancji
label: "Contact Form",
component: Component,
schema: {
form: fields.form({ label: "Formularz" }),
heading: fields.singleLine({ label: "Naglowek", defaultValue: "Skontaktuj sie" }),
description: fields.richText({ label: "Opis" }),
},
});Krok 3: Zbuduj komponent
Stworz blocks/contact-form/src/index.tsx. Komponent pobiera definicje formularza z Cmssy przez createCmssyClient (z @cmssy/react) i wysyla zgloszenie do publicznej mutacji formularza:
"use client";
import { useState } from "react";
import { createCmssyClient } from "@cmssy/react";
import type { CmssyBlockContext } from "@cmssy/react";
const client = createCmssyClient();
interface Props {
content: { form?: string; heading?: string; description?: string };
context?: CmssyBlockContext;
}
export default function ContactForm({ content, context }: Props) {
const { form: formId, heading = "Skontaktuj sie", description } = content;
const [status, setStatus] = useState<"idle" | "sending" | "success" | "error">("idle");
const [values, setValues] = useState<Record<string, string>>({});
const isEditor = context?.isEditor ?? false;
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (isEditor || !formId) return; // Nie wysylaj w podgladzie edytora
setStatus("sending");
try {
// Wysyla do cmssy: waliduje, zapisuje zgloszenie, wysyla email do odbiorcow
const res = await client.submitForm({ formId, data: values });
setStatus(res.success ? "success" : "error");
} catch {
setStatus("error");
}
};
if (status === "success") {
return (
<section className="py-24">
<div className="max-w-lg mx-auto text-center px-6">
<div className="w-16 h-16 mx-auto mb-6 rounded-full bg-green-100
flex items-center justify-center">
<svg width={32} height={32} fill="none" stroke="#22c55e"
viewBox="0 0 24 24" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round"
d="M5 13l4 4L19 7" />
</svg>
</div>
<h3 className="text-2xl font-bold mb-2">Dziekujemy! Odezwiemy sie wkrotce.</h3>
</div>
</section>
);
}
return (
<section className="py-24">
<div className="max-w-lg mx-auto px-6">
<div className="text-center mb-10">
<h2 className="text-3xl font-bold mb-3">{heading}</h2>
{description && (
<p className="text-muted-foreground">{description}</p>
)}
</div>
<form onSubmit={handleSubmit} className="space-y-5">
<div>
<label className="block text-sm font-medium mb-1.5">Imie</label>
<input
type="text"
required
value={values.name ?? ""}
onChange={(e) => setValues({ ...values, name: e.target.value })}
className="w-full px-4 py-2.5 border rounded-lg bg-background
focus:outline-none focus:ring-2 focus:ring-violet-500/20
focus:border-violet-500"
placeholder="Twoje imie"
/>
</div>
<div>
<label className="block text-sm font-medium mb-1.5">Email</label>
<input
type="email"
required
value={values.email ?? ""}
onChange={(e) => setValues({ ...values, email: e.target.value })}
className="w-full px-4 py-2.5 border rounded-lg bg-background
focus:outline-none focus:ring-2 focus:ring-violet-500/20
focus:border-violet-500"
placeholder="ty@przyklad.pl"
/>
</div>
<div>
<label className="block text-sm font-medium mb-1.5">Wiadomosc</label>
<textarea
required
rows={5}
value={values.message ?? ""}
onChange={(e) => setValues({ ...values, message: e.target.value })}
className="w-full px-4 py-2.5 border rounded-lg bg-background
resize-none focus:outline-none focus:ring-2
focus:ring-violet-500/20 focus:border-violet-500"
placeholder="Jak mozemy pomoc?"
/>
</div>
<button
type="submit"
disabled={status === "sending"}
className="w-full py-3 rounded-lg bg-violet-600 hover:bg-violet-700
text-white font-medium transition-colors
disabled:opacity-50 disabled:cursor-not-allowed"
>
{status === "sending" ? "Wysylanie..." : "Wyslij wiadomosc"}
</button>
{status === "error" && (
<p className="text-sm text-red-500 text-center">
Cos poszlo nie tak. Sprobuj ponownie.
</p>
)}
</form>
</div>
</section>
);
}Wskazowka: zamiast wpisywac pola na sztywno, mozesz pobrac definicje formularza przez klienta i renderowac form.fields dynamicznie - wtedy edycja formularza w Cmssy zmienia UI bez zmiany kodu.
Krok 4: Zarejestruj blok
Dodaj blok do rejestru blokow, aby edytor Cmssy wiedzial, ze istnieje. Stworz lub edytuj cmssy/blocks.ts:
import { contactFormBlock } from "@/blocks/contact-form/block";
export const blocks = [contactFormBlock];Ta tablica to jedyne zrodlo prawdy o tym, jakie bloki wspiera Twoja strona.
Krok 5: Wdroz strone
Cmssy nie hostuje Twojej strony - robisz to Ty. Wdroz aplikacje Next.js na Vercel (lub dowolny host) jak zwykle:
git push # nastepnie wdroz przez Vercel lub wybrane CIPo wdrozeniu wskaz workspace na adres na zywo, aby wizualny edytor Cmssy mogl osadzic Twoja strone do edycji.
Krok 6: Uzyj na stronie
W edytorze Cmssy:
- Kliknij przycisk + aby dodac blok
- Znajdz Contact Form na liscie blokow
- Przeciagnij go na strone
- W panelu bocznym wybierz formularz stworzony w Kroku 1
- Ustaw naglowek i opis
- Opublikuj strone
Zgloszenia trafiaja teraz do Dashboard → Forms, gdzie mozesz je przejrzec, oznaczyc jako przetworzone lub wyeksportowac.
Kluczowe wzorce
Bezpieczenstwo edytora
Zawsze sprawdzaj context.isEditor przed wyslaniem, aby formularz nie wysylal prawdziwych danych podczas podgladu przez redaktora:
const isEditor = context?.isEditor ?? false;
if (isEditor) return; // Pomin wysylke w podgladzie edytoraForm Builder = jedno zrodlo prawdy
Pola, walidacja i etykiety i18n zyja w Form Builderze. Twoj blok tylko je renderuje i wywoluje submitForm. Zmien formularz w Cmssy, a kazda strona, ktora go renderuje, pozostaje zsynchronizowana - bez redeployu dla zmian tresci.
Walidacja po stronie serwera
Publiczna mutacja submitForm waliduje pola po stronie serwera, zapisuje zgloszenie, wysyla powiadomienia email i wywoluje skonfigurowany webhook - dzieki czemu Twoj blok pozostaje cienki i bezpieczny.
Nastepne kroki
- Poznaj typy pol i walidacje w dokumentacji Forms & Form Builder
- Skonfiguruj projekt wedlug przewodnika instalacji
- Poznaj pelne API
defineBlock/fieldsw dokumentacji Blocks