Teraz z AI - twórz strony przez serwer MCP
Poradnik

Jak stworzyc blok formularza kontaktowego

Stworz formularz kontaktowy w Form Builderze Cmssy, a nastepnie wyrenderuj go we wlasnej headless stronie Next.js z @cmssy/react. Walidacja, zgloszenia i email po stronie Cmssy.

Z
Zespol Cmssy
12 min read

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.

Last updated: 15 marca 2026

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 CI

Po 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:

  1. Kliknij przycisk + aby dodac blok
  2. Znajdz Contact Form na liscie blokow
  3. Przeciagnij go na strone
  4. W panelu bocznym wybierz formularz stworzony w Kroku 1
  5. Ustaw naglowek i opis
  6. 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 edytora

Form 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