Przejdź do głównej zawartości

Przyjemny formularz feedback

Elo żelo! Czy zastanawiałeś się kiedyś, jak zrobić formularz feedbacku, który będzie przyjemny dla użytkownika, a jednocześnie szybki w wykonaniu dla ciebie, bez konieczności tworzenia panelu administracyjnego u siebie? W tym poradniku dowiesz się, jak to osiągnąć z wykorzystaniem Twojego ulubionego frameworku Next.js (lub dowolnego innego, nawet zwykłego HTML-a) oraz backendu w postaci Formularzy Google.

Zamysł jest taki, aby stworzyć w pełni własny formularz na swojej stronie, a odpowiedzi przeglądać wygodnie na stronie Formularzy Google lub w Arkuszach Google. Dzięki temu zaoszczędzisz czas na tworzeniu infrastruktury, jednocześnie zachowując pełną kontrolę nad wyglądem i działaniem formularza.

Założenia

Technologie użyte:

  1. NextJS jako przykład frameworku na frontcie
  2. Google Forms jako backend z odpowiedziami
  3. shadcn/ui jako biblioteka komponentów

tl;dr

Wszystko sprowadza się do wysłania formularza POST do Google Forms z odpowiednimi danymi:

<form action="https://docs.google.com/forms/.../formResponse" method="POST">
<input type="text" name="entry.123456789" />
<input type="text" name="entry.987654321" />
<button type="submit">Submit</button>
</form>

Aczkolwiek zrobimy to w bardziej elegancki sposób z użyciem react-hook-form i shadcn/ui oraz zabezpieczeniem, przeciwko spamowaniu.

Let’s gooo 🚀

To są szybkie kroki, dzięki którym stworzysz formularz feedbacku:

  1. Stwórz nowy formularz:

    Przejdź na stronę Google Forms, stwórz nowy formularz i dodaj swoje pytania.

  2. Opublikuj formularz i stwórz arkusz

    Następnie opublikuj publicznie swój forms:

    • Przycisk “Opublikuj”
    • W kategorii respondenci kliknij “Zarządzaj”
    • ‘Dostęp ogólny -> Widok respondenta’ zmień z “Politechnika Wrocławska” na “Każda osoba mająca link”
    • “Gotowe” -> “Opublikuj”
  3. Stwórz połączony arkusz google

    Możesz przeglądać również odpowiedzi i łatwo je udostępniać dzięki arkuszom Google. Wystarczy, że po przejściu do zakładki odpowiedzi,
    klikniesz zielony przycisk “Link do Arkuszy” -> “Utwórz”:

  4. Nowy komponent na stronie

    Naszedł czas na dodanie formularza do swojej strony. Przejdźmy do Twojego projektu NextJS, stwórzmy nowy client komponent FeedbackForm.tsx:

    FeedbackForm.tsx
    "use client";
    import React from "react";
    export function FeedbackForm() {
    return <div>Witam</div>;
    }
  5. Wymagane dependencies

    Do obsługi <form> użyjmy react-hook-form i komponentu shadcn/ui Form. Najpierw dodaj ten komponent do projektu:

    Okno terminala
    npx shadcn@latest add form
  6. Schemat

    Aby upewnić się, że użytkownik wysyła tylko takie dane, które są wymagane, stwórzmy schemat formularza:

    schemas.ts
    import { z } from "zod";
    export const FeedbackFormSchema = z.object({
    email: z.string(),
    content: z.string(),
    });
  7. Formularz

    Następnie dodajmy formularz do naszego komponentu:

    FeedbackForm.tsx
    "use client";
    import { zodResolver } from "@hookform/resolvers/zod";
    import React from "react";
    import { useForm } from "react-hook-form";
    import type { z } from "zod";
    import { Button } from "@/components/ui/button";
    import {
    Form,
    FormControl,
    FormField,
    FormItem,
    FormLabel,
    FormMessage,
    } from "@/components/ui/form";
    import { Input } from "@/components/ui/input";
    import { Textarea } from "@/components/ui/textarea";
    import { FeedbackFormSchema } from "@/types/schemas";
    export function FeedbackForm() {
    const form = useForm<z.infer<typeof FeedbackFormSchema>>({
    resolver: zodResolver(FeedbackFormSchema),
    defaultValues: {
    email: "",
    content: "",
    },
    });
    async function onSubmit(values: z.infer<typeof FeedbackFormSchema>) {
    // Do something with the form values
    }
    return (
    <div className="m-10 w-full rounded-md border p-5 sm:max-w-[425px]">
    <h1 className="text-lg font-semibold">Formularz</h1>
    <Form {...form}>
    <form
    onSubmit={form.handleSubmit(onSubmit)}
    className="grid w-full gap-4 py-4 sm:max-w-[425px]"
    >
    <FormField
    control={form.control}
    name="email"
    render={({ field }) => (
    <FormItem>
    <FormLabel>Adres email</FormLabel>
    <FormControl>
    <Input
    placeholder="123456@student.pwr.edu.pl"
    {...field}
    />
    </FormControl>
    <FormMessage />
    </FormItem>
    )}
    />
    <FormField
    control={form.control}
    name="content"
    render={({ field }) => (
    <FormItem>
    <FormLabel>Treść</FormLabel>
    <FormControl>
    <Textarea placeholder="Treść zgłoszenia" {...field} />
    </FormControl>
    <FormMessage />
    </FormItem>
    )}
    />
    <Button type="submit">Prześlij zgłoszenie</Button>
    </form>
    </Form>
    </div>
    );
    }

    Efekt tego wygląda mniej więcej tak:

  8. Backend

    Aby cała magia mogła się zadziać, stwórzmy Server Action, która będzie wysyłać dane do Google.

    actions/feedback.ts
    "use server";
    import type { z } from "zod";
    import { env } from "@/env.mjs";
    import { FeedbackFormSchema } from "@/types/schemas";
    export const sendFeedbackForm = async (
    values: z.infer<typeof FeedbackFormSchema>
    ) => {
    try {
    FeedbackFormSchema.parse(values);
    // Jakaś funkcja do sprawdzania rate-limitów, czy użytkownik nie spamuje
    // const isSpam = await checkSpam(values);
    // if (isSpam) { return false } czy coś
    const googleFormUrl = process.env.FORMS_LINK;
    const formData = new URLSearchParams();
    formData.append(process.env.FORMS_email, values.email);
    formData.append(process.env.FORMS_content, values.content);
    const response = await fetch(googleFormUrl, {
    method: "POST",
    body: formData,
    headers: {
    "Content-Type": "application/x-www-form-urlencoded",
    },
    });
    // Obsługa odpowiedzi
    if (response.ok) {
    return true;
    } else {
    console.error("Error while sending feedback form", response);
    }
    } catch (error) {
    console.error("Error while sending feedback form", error);
    }
    };
  9. Zdobycie potrzebnych danych

    Aby uzupełnić .env o potrzebne dane, przejdź do swojego formularza Google i w górnym topbarze po prawej kliknij ikonke oka, czyli podgląd:

    Następnie otwórz narzędzia deweloperskie przeglądarki i wybierz zakładkę Elements. Następnie inspect toolem (Ctrl + Shift + C) najedź na formularz i znajdź element form:

    Skopiuj link z action i zapisz do zmiennej FORMS_LINK w env.

    Następnie wyszukaj Ctrl + F w devtoolsach (nie na stronie!) entry. aby znaleźć dwa pola (jeśli tyle miałeś w formularzu) i skopiuj je do odpowiednich zmiennych. Są one ułożone zwykle w dobrej kolejności:

    Przykładowy .env:

    .env
    FORMS_LINK=https://docs.google.com/forms/.../formResponse
    FORMS_email=entry.123456789
    FORMS_content=entry.987654321
  10. Połączenie formularza z backendem

    Na sam koniec zostało połączenie formularza z backendem. Wystarczy dodać Server Action do naszego formularza:

    FeedbackForm.tsx
    "use client";
    import { zodResolver } from "@hookform/resolvers/zod";
    import React from "react";
    import { useForm } from "react-hook-form";
    import type { z } from "zod";
    import { Button } from "@/components/ui/button";
    import {
    Form,
    FormControl,
    FormField,
    FormItem,
    FormLabel,
    FormMessage,
    } from "@/components/ui/form";
    import { Input } from "@/components/ui/input";
    import { FeedbackFormSchema } from "@/types/schemas";
    import { sendFeedbackForm } from "@/actions/feedback";
    export function FeedbackForm() {
    const form = useForm<z.infer<typeof FeedbackFormSchema>>({
    resolver: zodResolver(FeedbackFormSchema),
    defaultValues: {
    email: "",
    content: "",
    },
    });
    async function onSubmit(values: z.infer<typeof FeedbackFormSchema>) {
    await sendFeedbackForm(values);
    }
    return (
    <div className="m-10 w-full rounded-md border p-5 sm:max-w-[425px]">
    <h1 className="text-lg font-semibold">Formularz</h1>
    <Form {...form}>
    <form
    onSubmit={form.handleSubmit(onSubmit)}
    className="grid w-full gap-4 py-4 sm:max-w-[425px]"
    >
    <FormField
    control={form.control}
    name="email"
    render={({ field }) => (
    <FormItem>
    <FormLabel>Opcja 1</FormLabel>
    <FormControl>
    <Input
    placeholder="np. Błąd związany z formularzem"
    {...field}
    />
    </FormControl>
    <FormMessage />
    </FormItem>
    )}
    />
    <FormField
    control={form.control}
    name="content"
    render={({ field }) => (
    <FormItem>
    <FormLabel>Opcja 2</FormLabel>
    <FormControl>
    <Input
    placeholder="np. Błąd związany z formularzem"
    {...field}
    />
    </FormControl>
    <FormMessage />
    </FormItem>
    )}
    />
    <Button type="submit">Prześlij zgłoszenie</Button>
    </form>
    </Form>
    </div>
    );
    }

That’s it

Na koniec możesz do tego dodać jakieś ładowanie, komunikaty o sukcesie, błędach, użyć react-query, disable przycisk i pola w czasie wysylania itp. ale to już zależy od Ciebie. Teraz możesz cieszyć się swoim własnym formularzem feedbacku, który jest szybki i przyjemny dla użytkownika.