Skip to content

Commit f369a73

Browse files
authored
feat: feedback tutorial (#33)
* feat: feedback tutorial * fix: desc * fix: content fix * fix: content & sections
1 parent b11a557 commit f369a73

File tree

9 files changed

+375
-0
lines changed

9 files changed

+375
-0
lines changed

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
"format": "prettier --write .",
1313
"prepare": "husky"
1414
},
15+
"imports": {
16+
"#assets/*": "./src/assets/*?url",
17+
"#components/*": "./src/components/*.astro"
18+
},
1519
"dependencies": {
1620
"@astrojs/check": "^0.9.4",
1721
"@astrojs/react": "^3.6.3",
36.7 KB
Loading
107 KB
Loading
38.7 KB
Loading
20.2 KB
Loading
14.4 KB
Loading
57.6 KB
Loading
18.1 KB
Loading
Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,371 @@
1+
---
2+
title: Przyjemny formularz feedback
3+
sidebar:
4+
order: 7
5+
description: Jak utworzyć formularz feedbacku, który będzie przyjemny dla użytkownika i szybki w wykonaniu dla ciebie bez konieczności tworzenia panelu administracyjnego u siebie.
6+
---
7+
8+
import { Steps } from "@astrojs/starlight/components";
9+
import ImageFeedback1 from "#assets/tutorials/feedback-1.png";
10+
import ImageFeedback2 from "#assets/tutorials/feedback-2.png";
11+
import ImageFeedback3 from "#assets/tutorials/feedback-3.png";
12+
import ImageFeedback4 from "#assets/tutorials/feedback-4.png";
13+
import ImageFeedback5 from "#assets/tutorials/feedback-5.png";
14+
import ImageFeedback6 from "#assets/tutorials/feedback-6.png";
15+
import ImageFeedback7 from "#assets/tutorials/feedback-7.png";
16+
17+
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.
18+
19+
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.
20+
21+
## Założenia
22+
23+
Technologie użyte:
24+
25+
1. NextJS jako przykład frameworku na frontcie
26+
2. Google Forms jako backend z odpowiedziami
27+
3. shadcn/ui jako biblioteka komponentów
28+
29+
## tl;dr
30+
31+
Wszystko sprowadza się do wysłania formularza POST do Google Forms z odpowiednimi danymi:
32+
33+
```html
34+
<form action="https://docs.google.com/forms/.../formResponse" method="POST">
35+
<input type="text" name="entry.123456789" />
36+
<input type="text" name="entry.987654321" />
37+
<button type="submit">Submit</button>
38+
</form>
39+
```
40+
41+
Aczkolwiek zrobimy to w bardziej elegancki sposób z użyciem `react-hook-form` i `shadcn/ui` oraz zabezpieczeniem, przeciwko spamowaniu.
42+
43+
## Let's gooo 🚀
44+
45+
To są szybkie kroki, dzięki którym stworzysz formularz feedbacku:
46+
47+
<Steps>
48+
49+
1. **Stwórz nowy formularz:**
50+
51+
Przejdź na stronę [Google Forms](https://docs.google.com/forms/u/0/), stwórz nowy formularz i dodaj swoje pytania.
52+
53+
<img src={ImageFeedback1} width={"100%"} height={"auto"} />
54+
55+
2. **Opublikuj formularz i stwórz arkusz**
56+
57+
Następnie opublikuj publicznie swój forms:
58+
59+
- Przycisk "Opublikuj"
60+
- W kategorii respondenci kliknij "Zarządzaj"
61+
- 'Dostęp ogólny -> Widok respondenta' zmień z "Politechnika Wrocławska" na "Każda osoba mająca link"
62+
- "Gotowe" -> "Opublikuj"
63+
64+
<img src={ImageFeedback2} width={"100%"} height={"auto"} />
65+
66+
3. **Stwórz połączony arkusz google**
67+
68+
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, <br/>klikniesz zielony przycisk "Link do Arkuszy" -> "Utwórz":
69+
70+
<img src={ImageFeedback3} width={"100%"} height={"auto"} />
71+
72+
:::tip
73+
Arkusz ten będzie automatycznie aktualizowany o nowe odpowiedzi oraz zmiany w formularzu, więc nie musisz się o to martwić. Możesz go wygodnie udostępniać innym osobom.
74+
:::
75+
76+
4. **Nowy komponent na stronie**
77+
78+
Naszedł czas na dodanie formularza do swojej strony. Przejdźmy do Twojego projektu NextJS, stwórzmy nowy client komponent `FeedbackForm.tsx`:
79+
80+
```tsx
81+
// FeedbackForm.tsx
82+
"use client";
83+
84+
import React from "react";
85+
86+
export function FeedbackForm() {
87+
return <div>Witam</div>;
88+
}
89+
```
90+
91+
5. **Wymagane dependencies**
92+
93+
Do obsługi `<form>` użyjmy `react-hook-form` i komponentu shadcn/ui `Form`.
94+
Najpierw dodaj ten komponent do projektu:
95+
96+
```bash
97+
npx shadcn@latest add form
98+
```
99+
100+
6. **Schemat**
101+
102+
Aby upewnić się, że użytkownik wysyła tylko takie dane, które są wymagane, stwórzmy schemat formularza:
103+
104+
```tsx
105+
// schemas.ts
106+
import { z } from "zod";
107+
108+
export const FeedbackFormSchema = z.object({
109+
email: z.string(),
110+
content: z.string(),
111+
});
112+
```
113+
114+
7. **Formularz**
115+
116+
Następnie dodajmy formularz do naszego komponentu:
117+
118+
```tsx
119+
// FeedbackForm.tsx
120+
"use client";
121+
122+
import { zodResolver } from "@hookform/resolvers/zod";
123+
import React from "react";
124+
import { useForm } from "react-hook-form";
125+
import type { z } from "zod";
126+
127+
import { Button } from "@/components/ui/button";
128+
import {
129+
Form,
130+
FormControl,
131+
FormField,
132+
FormItem,
133+
FormLabel,
134+
FormMessage,
135+
} from "@/components/ui/form";
136+
import { Input } from "@/components/ui/input";
137+
import { Textarea } from "@/components/ui/textarea";
138+
import { FeedbackFormSchema } from "@/types/schemas";
139+
140+
export function FeedbackForm() {
141+
const form = useForm<z.infer<typeof FeedbackFormSchema>>({
142+
resolver: zodResolver(FeedbackFormSchema),
143+
defaultValues: {
144+
email: "",
145+
content: "",
146+
},
147+
});
148+
149+
async function onSubmit(values: z.infer<typeof FeedbackFormSchema>) {
150+
// Do something with the form values
151+
}
152+
return (
153+
<div className="m-10 w-full rounded-md border p-5 sm:max-w-[425px]">
154+
<h1 className="text-lg font-semibold">Formularz</h1>
155+
<Form {...form}>
156+
<form
157+
onSubmit={form.handleSubmit(onSubmit)}
158+
className="grid w-full gap-4 py-4 sm:max-w-[425px]"
159+
>
160+
<FormField
161+
control={form.control}
162+
name="email"
163+
render={({ field }) => (
164+
<FormItem>
165+
<FormLabel>Adres email</FormLabel>
166+
<FormControl>
167+
<Input
168+
placeholder="123456@student.pwr.edu.pl"
169+
{...field}
170+
/>
171+
</FormControl>
172+
<FormMessage />
173+
</FormItem>
174+
)}
175+
/>
176+
<FormField
177+
control={form.control}
178+
name="content"
179+
render={({ field }) => (
180+
<FormItem>
181+
<FormLabel>Treść</FormLabel>
182+
<FormControl>
183+
<Textarea placeholder="Treść zgłoszenia" {...field} />
184+
</FormControl>
185+
<FormMessage />
186+
</FormItem>
187+
)}
188+
/>
189+
<Button type="submit">Prześlij zgłoszenie</Button>
190+
</form>
191+
</Form>
192+
</div>
193+
);
194+
}
195+
```
196+
197+
:::tip
198+
Jest to podstawowa metoda tworzenia formularzy z `shadcn/ui` i `react-hook-form`. Jeśli nie rozumiesz tego kodu, zachęcam do odwiedzenia [dokumentacji shadcn/ui](https://ui.shadcn.com/docs/components/form).
199+
:::
200+
201+
Efekt tego wygląda mniej więcej tak:
202+
203+
{" "}
204+
205+
<img src={ImageFeedback4} width={"100%"} height={"auto"} />
206+
207+
8. **Backend**
208+
209+
Aby cała magia mogła się zadziać, stwórzmy `Server Action`, która będzie wysyłać dane do Google.
210+
211+
```tsx
212+
// actions/feedback.ts
213+
"use server";
214+
215+
import type { z } from "zod";
216+
217+
import { env } from "@/env.mjs";
218+
import { FeedbackFormSchema } from "@/types/schemas";
219+
220+
export const sendFeedbackForm = async (
221+
values: z.infer<typeof FeedbackFormSchema>
222+
) => {
223+
try {
224+
FeedbackFormSchema.parse(values);
225+
226+
// Jakaś funkcja do sprawdzania rate-limitów, czy użytkownik nie spamuje
227+
// const isSpam = await checkSpam(values);
228+
// if (isSpam) { return false } czy coś
229+
230+
const googleFormUrl = process.env.FORMS_LINK;
231+
232+
const formData = new URLSearchParams();
233+
formData.append(process.env.FORMS_email, values.email);
234+
formData.append(process.env.FORMS_content, values.content);
235+
236+
const response = await fetch(googleFormUrl, {
237+
method: "POST",
238+
body: formData,
239+
headers: {
240+
"Content-Type": "application/x-www-form-urlencoded",
241+
},
242+
});
243+
244+
// Obsługa odpowiedzi
245+
if (response.ok) {
246+
return true;
247+
} else {
248+
console.error("Error while sending feedback form", response);
249+
}
250+
} catch (error) {
251+
console.error("Error while sending feedback form", error);
252+
}
253+
};
254+
```
255+
256+
9. **Zdobycie potrzebnych danych**
257+
258+
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:
259+
260+
<img src={ImageFeedback5} width={"100%"} height={"auto"} />
261+
262+
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`:
263+
264+
<img src={ImageFeedback6} width={"100%"} height={"auto"} />
265+
266+
Skopiuj link z `action` i zapisz do zmiennej `FORMS_LINK` w env.
267+
268+
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:
269+
270+
<img src={ImageFeedback7} width={"100%"} height={"auto"} />
271+
272+
Przykładowy `.env`:
273+
274+
```
275+
// .env
276+
FORMS_LINK=https://docs.google.com/forms/.../formResponse
277+
FORMS_email=entry.123456789
278+
FORMS_content=entry.987654321
279+
```
280+
281+
10. **Połączenie formularza z backendem**
282+
283+
Na sam koniec zostało połączenie formularza z backendem. Wystarczy dodać `Server Action` do naszego formularza:
284+
285+
```tsx {20, 32}
286+
// FeedbackForm.tsx
287+
"use client";
288+
289+
import { zodResolver } from "@hookform/resolvers/zod";
290+
import React from "react";
291+
import { useForm } from "react-hook-form";
292+
import type { z } from "zod";
293+
294+
import { Button } from "@/components/ui/button";
295+
import {
296+
Form,
297+
FormControl,
298+
FormField,
299+
FormItem,
300+
FormLabel,
301+
FormMessage,
302+
} from "@/components/ui/form";
303+
import { Input } from "@/components/ui/input";
304+
import { FeedbackFormSchema } from "@/types/schemas";
305+
import { sendFeedbackForm } from "@/actions/feedback";
306+
307+
export function FeedbackForm() {
308+
const form = useForm<z.infer<typeof FeedbackFormSchema>>({
309+
resolver: zodResolver(FeedbackFormSchema),
310+
defaultValues: {
311+
email: "",
312+
content: "",
313+
},
314+
});
315+
316+
async function onSubmit(values: z.infer<typeof FeedbackFormSchema>) {
317+
await sendFeedbackForm(values);
318+
}
319+
return (
320+
<div className="m-10 w-full rounded-md border p-5 sm:max-w-[425px]">
321+
<h1 className="text-lg font-semibold">Formularz</h1>
322+
<Form {...form}>
323+
<form
324+
onSubmit={form.handleSubmit(onSubmit)}
325+
className="grid w-full gap-4 py-4 sm:max-w-[425px]"
326+
>
327+
<FormField
328+
control={form.control}
329+
name="email"
330+
render={({ field }) => (
331+
<FormItem>
332+
<FormLabel>Opcja 1</FormLabel>
333+
<FormControl>
334+
<Input
335+
placeholder="np. Błąd związany z formularzem"
336+
{...field}
337+
/>
338+
</FormControl>
339+
<FormMessage />
340+
</FormItem>
341+
)}
342+
/>
343+
<FormField
344+
control={form.control}
345+
name="content"
346+
render={({ field }) => (
347+
<FormItem>
348+
<FormLabel>Opcja 2</FormLabel>
349+
<FormControl>
350+
<Input
351+
placeholder="np. Błąd związany z formularzem"
352+
{...field}
353+
/>
354+
</FormControl>
355+
<FormMessage />
356+
</FormItem>
357+
)}
358+
/>
359+
<Button type="submit">Prześlij zgłoszenie</Button>
360+
</form>
361+
</Form>
362+
</div>
363+
);
364+
}
365+
```
366+
367+
</Steps>
368+
369+
## That's it
370+
371+
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.

0 commit comments

Comments
 (0)