style(forms): ujednolicenie ekranu sukcesu i usuniecie zbednych opcji

This commit is contained in:
MichalC
2026-07-27 22:32:30 +02:00
parent e338c95da0
commit 1a11aecf01
5 changed files with 78 additions and 72 deletions
+14 -14
View File
@@ -4,8 +4,9 @@ import { useCartStore } from '@/lib/cart-store';
import { useActionState, useEffect, startTransition } from 'react'; import { useActionState, useEffect, startTransition } from 'react';
import Link from 'next/link'; import Link from 'next/link';
import { submitOrderForm } from './actions'; import { submitOrderForm } from './actions';
import { ArrowLeft, CheckCircle } from 'lucide-react'; import { ArrowLeft, CheckCircle2 } from 'lucide-react';
import { buttonClasses } from '@/components/ui/Button'; import { buttonClasses } from '@/components/ui/Button';
import { getRecaptchaToken } from '@/lib/getRecaptchaToken';
const initialState = { success: false, message: '' }; const initialState = { success: false, message: '' };
@@ -34,14 +35,16 @@ Wróć do sklepu
if (state.success) { if (state.success) {
return ( return (
<main className="min-h-screen bg-slate-50 flex items-center justify-center"> <main className="min-h-screen bg-slate-50 flex items-center justify-center py-16">
<div className="text-center max-w-md px-4"> <div className="max-w-md mx-auto px-4 w-full">
<CheckCircle className="size-16 text-green-500 mx-auto mb-4" /> <div className="bg-green-50 text-green-800 p-8 rounded-2xl border border-green-200 flex flex-col items-center text-center">
<h2 className="text-2xl font-bold text-primary font-heading mb-2">Zamówienie przyjęte!</h2> <CheckCircle2 className="w-16 h-16 text-green-500 mb-4" />
<p className="text-text-muted mb-6">{state.message}</p> <h3 className="text-2xl font-bold font-heading mb-2">Zamówienie przyjęte!</h3>
<Link href="/sklep" className={buttonClasses({ variant: 'primary', size: 'md' })}> <p className="text-green-700">{state.message}</p>
Wróć do sklepu <Link href="/sklep" className="mt-6 px-6 py-2 bg-green-600 text-white rounded-lg font-semibold hover:bg-green-700 transition-colors">
</Link> Wróć do sklepu
</Link>
</div>
</div> </div>
</main> </main>
); );
@@ -78,11 +81,8 @@ Wróć do sklepu
<form onSubmit={async (e) => { <form onSubmit={async (e) => {
e.preventDefault(); e.preventDefault();
const formData = new FormData(e.currentTarget); const formData = new FormData(e.currentTarget);
if (process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY) { const token = await getRecaptchaToken('submit');
const token = await (window as any).grecaptcha.execute( if (token) {
process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY,
{ action: 'submit' }
);
formData.append('recaptchaToken', token); formData.append('recaptchaToken', token);
} }
startTransition(() => { startTransition(() => {
+26 -36
View File
@@ -1,10 +1,11 @@
'use client'; 'use client';
import { useState } from 'react'; import { useState } from 'react';
import { AlertTriangle, Wrench, Phone, Check, Clock, Zap } from 'lucide-react'; import { Wrench, Zap, Info, Clock, Check, AlertTriangle, Phone, CheckCircle2 } from 'lucide-react';
import Link from 'next/link'; import Link from 'next/link';
import { companyDetails } from '@/content/company'; import { companyDetails } from '@/content/company';
import { submitAwariaForm } from '@/app/zgloszenie-awarii/actions'; import { submitAwariaForm } from '@/app/zgloszenie-awarii/actions';
import { getRecaptchaToken } from '@/lib/getRecaptchaToken';
// Kody błędów popularnych marek // Kody błędów popularnych marek
const ERROR_CODES: Record<string, Record<string, string>> = { const ERROR_CODES: Record<string, Record<string, string>> = {
@@ -125,15 +126,8 @@ export function AwariaClient() {
} }
setIsSubmitting(true); setIsSubmitting(true);
let recaptchaToken = ''; const recaptchaToken = await getRecaptchaToken('submit');
if (process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY) { const formDataObj: any = {
recaptchaToken = await (window as any).grecaptcha.execute(
process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY,
{ action: 'submit' }
);
}
await submitAwariaForm({
isUrgent, isUrgent,
brand, brand,
model, model,
@@ -147,8 +141,10 @@ export function AwariaClient() {
city, city,
preferredDate: '', preferredDate: '',
preferredTime: '', preferredTime: '',
recaptchaToken, };
}); if (recaptchaToken) formDataObj.recaptchaToken = recaptchaToken;
await submitAwariaForm(formDataObj);
setIsSubmitting(false); setIsSubmitting(false);
setStep('success'); setStep('success');
@@ -157,31 +153,25 @@ export function AwariaClient() {
if (step === 'success') { if (step === 'success') {
return ( return (
<main className="bg-slate-50 min-h-screen flex items-center justify-center py-16"> <main className="bg-slate-50 min-h-screen flex items-center justify-center py-16">
<div className="max-w-md mx-auto px-4 text-center"> <div className="max-w-md mx-auto px-4 w-full">
<div className="size-20 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-6"> <div className="bg-green-50 text-green-800 p-8 rounded-2xl border border-green-200 flex flex-col items-center text-center">
<Check className="size-10 text-green-600" /> <CheckCircle2 className="w-16 h-16 text-green-500 mb-4" />
</div> <h3 className="text-2xl font-bold font-heading mb-2">Zgłoszenie przyjęte!</h3>
<h2 className="text-2xl font-extrabold text-primary font-heading mb-3"> <p className="text-green-700 mb-2">
Zgłoszenie przyjęte! {isUrgent
</h2> ? 'Twoje zgłoszenie oznaczono jako PILNE — skontaktujemy się z Tobą w ciągu 2 godzin.'
<p className="text-text-muted mb-2"> : 'Skontaktujemy się z Tobą w ciągu 24 godzin, aby ustalić termin wizyty serwisanta.'}
{isUrgent </p>
? 'Twoje zgłoszenie oznaczono jako PILNE — skontaktujemy się z Tobą w ciągu 2 godzin.' <p className="text-sm text-green-700/80 mb-6">
: 'Skontaktujemy się z Tobą w ciągu 24 godzin, aby ustalić termin wizyty serwisanta.'} Jeśli sprawa jest pilna — zadzwoń bezpośrednio: {companyDetails.phone}
</p> </p>
<p className="text-sm text-text-muted mb-8"> <button
Jeśli sprawa jest pilna — zadzwoń bezpośrednio: onClick={() => setStep('form')}
</p> className="mt-2 px-6 py-2 bg-green-600 text-white rounded-lg font-semibold hover:bg-green-700 transition-colors"
<a href={`tel:${companyDetails.phoneRaw}`}> >
<button className="flex items-center gap-2 px-6 py-3 bg-accent text-white rounded-xl font-bold mx-auto hover:bg-accent/90 transition-colors mb-4"> Wyślij kolejne zgłoszenie
<Phone className="size-4" /> {companyDetails.phone}
</button> </button>
</a> </div>
<Link href="/" className="text-sm text-text-muted hover:text-accent transition-colors">
Wróć na stronę główną
</Link>
</div> </div>
</main> </main>
); );
+3 -8
View File
@@ -6,6 +6,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod'; import { z } from 'zod';
import { Loader2, CheckCircle2, AlertCircle, Check } from 'lucide-react'; import { Loader2, CheckCircle2, AlertCircle, Check } from 'lucide-react';
import { submitContactForm } from '@/app/kontakt/actions'; import { submitContactForm } from '@/app/kontakt/actions';
import { getRecaptchaToken } from '@/lib/getRecaptchaToken';
const contactFormSchema = z.object({ const contactFormSchema = z.object({
name: z.string().min(2, "Imię musi mieć min. 2 znaki"), name: z.string().min(2, "Imię musi mieć min. 2 znaki"),
@@ -49,11 +50,8 @@ export function ContactForm() {
}); });
formData.append("consent", consent ? 'true' : 'false'); formData.append("consent", consent ? 'true' : 'false');
if (process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY) { const token = await getRecaptchaToken('submit');
const token = await (window as any).grecaptcha.execute( if (token) {
process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY,
{ action: 'submit' }
);
formData.append('recaptchaToken', token); formData.append('recaptchaToken', token);
} }
@@ -187,9 +185,6 @@ export function ContactForm() {
<option value="">Wybierz temat...</option> <option value="">Wybierz temat...</option>
<option value="klimatyzacja">Klimatyzacja</option> <option value="klimatyzacja">Klimatyzacja</option>
<option value="pompy-ciepla">Pompy ciepła</option> <option value="pompy-ciepla">Pompy ciepła</option>
<option value="remonty">Remonty i wykończenia</option>
<option value="hydraulika">Hydraulika</option>
<option value="serwis-agd">Serwis AGD</option>
<option value="inne">Inne zapytanie</option> <option value="inne">Inne zapytanie</option>
</select> </select>
{errors.subject && <p className="text-red-500 text-xs font-medium">{errors.subject.message}</p>} {errors.subject && <p className="text-red-500 text-xs font-medium">{errors.subject.message}</p>}
+14 -14
View File
@@ -1,8 +1,9 @@
'use client'; 'use client';
import { useState } from 'react'; import { useState } from 'react';
import { Check, Loader2 } from 'lucide-react'; import { Check, Loader2, CheckCircle2 } from 'lucide-react';
import { submitMultisplitForm } from '@/app/zapytanie-multisplit/actions'; import { submitMultisplitForm } from '@/app/zapytanie-multisplit/actions';
import { getRecaptchaToken } from '@/lib/getRecaptchaToken';
interface Props { interface Props {
productName: string; productName: string;
@@ -39,13 +40,8 @@ export default function MultisplitContactForm({ productName, productSku }: Props
setLoading(true); setLoading(true);
setError(''); setError('');
try { try {
let recaptchaToken = ''; const token = await getRecaptchaToken('submit');
if (process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY) { const recaptchaToken = token || '';
recaptchaToken = await (window as any).grecaptcha.execute(
process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY,
{ action: 'submit' }
);
}
const res = await submitMultisplitForm({ const res = await submitMultisplitForm({
name: form.name, name: form.name,
@@ -69,12 +65,16 @@ export default function MultisplitContactForm({ productName, productSku }: Props
if (sent) { if (sent) {
return ( return (
<div className="flex flex-col items-center justify-center gap-3 py-12 text-center"> <div className="bg-green-50 text-green-800 p-8 rounded-2xl border border-green-200 flex flex-col items-center text-center">
<div className="size-12 bg-green-100 rounded-full flex items-center justify-center"> <CheckCircle2 className="w-16 h-16 text-green-500 mb-4" />
<Check className="size-6 text-green-600" /> <h3 className="text-2xl font-bold font-heading mb-2">Zapytanie wysłane!</h3>
</div> <p className="text-green-700">Oddzwonimy w ciągu 24 godzin z bezpłatną wyceną.</p>
<p className="font-bold text-primary">Zapytanie wysłane!</p> <button
<p className="text-sm text-text-muted">Oddzwonimy w ciągu 24 godzin z bezpłatną wyceną.</p> onClick={() => setSent(false)}
className="mt-6 px-6 py-2 bg-green-600 text-white rounded-lg font-semibold hover:bg-green-700 transition-colors"
>
Wyślij kolejne zapytanie
</button>
</div> </div>
); );
} }
+21
View File
@@ -0,0 +1,21 @@
'use client';
export function getRecaptchaToken(action: string = 'submit'): Promise<string | null> {
return new Promise((resolve) => {
const siteKey = process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY;
if (typeof window === 'undefined' || !siteKey || !(window as any).grecaptcha) {
resolve(null);
return;
}
try {
(window as any).grecaptcha.ready(() => {
(window as any).grecaptcha
.execute(siteKey, { action })
.then((token: string) => resolve(token))
.catch(() => resolve(null));
});
} catch {
resolve(null);
}
});
}