style(forms): ujednolicenie ekranu sukcesu i usuniecie zbednych opcji
This commit is contained in:
+12
-12
@@ -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,15 +35,17 @@ 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>
|
||||||
|
<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">
|
||||||
Wróć do sklepu
|
Wróć do sklepu
|
||||||
</Link>
|
</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(() => {
|
||||||
|
|||||||
@@ -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!
|
|
||||||
</h2>
|
|
||||||
<p className="text-text-muted mb-2">
|
|
||||||
{isUrgent
|
{isUrgent
|
||||||
? 'Twoje zgłoszenie oznaczono jako PILNE — skontaktujemy się z Tobą w ciągu 2 godzin.'
|
? 'Twoje zgłoszenie oznaczono jako PILNE — skontaktujemy się z Tobą w ciągu 2 godzin.'
|
||||||
: 'Skontaktujemy się z Tobą w ciągu 24 godzin, aby ustalić termin wizyty serwisanta.'}
|
: 'Skontaktujemy się z Tobą w ciągu 24 godzin, aby ustalić termin wizyty serwisanta.'}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm text-text-muted mb-8">
|
<p className="text-sm text-green-700/80 mb-6">
|
||||||
Jeśli sprawa jest pilna — zadzwoń bezpośrednio:
|
Jeśli sprawa jest pilna — zadzwoń bezpośrednio: {companyDetails.phone}
|
||||||
</p>
|
</p>
|
||||||
<a href={`tel:${companyDetails.phoneRaw}`}>
|
<button
|
||||||
<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">
|
onClick={() => setStep('form')}
|
||||||
<Phone className="size-4" /> {companyDetails.phone}
|
className="mt-2 px-6 py-2 bg-green-600 text-white rounded-lg font-semibold hover:bg-green-700 transition-colors"
|
||||||
|
>
|
||||||
|
Wyślij kolejne zgłoszenie
|
||||||
</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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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>}
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user