- Filtr 'Ilość jednostek wewn.' w sklepie (1–9 jednostek, checkboxy, liczniki) - Komponent RecentlyViewedProducts + Zustand store (recently-viewed-store.ts) - Pompy ciepła dodane do sklepu i nawigacji (Header + MobileMenu) - Logotypy marek zaktualizowane (Daikin, Panasonic + odświeżone istniejące) - Checkbox zgody RODO w /zamowienie powiększony (w-5 h-5) - Usunięcie ~65 zbędnych komentarzy WHAT z 12 plików (build: 0 errors) - Plik analyze_products.ts (pomocniczy skrypt diagnostyczny)
399 lines
18 KiB
TypeScript
399 lines
18 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { Wrench, Zap, Check, AlertTriangle, Phone, CheckCircle2 } from 'lucide-react';
|
|
import { companyDetails } from '@/content/company';
|
|
import { submitAwariaForm } from '@/app/zgloszenie-awarii/actions';
|
|
import { getRecaptchaToken } from '@/lib/getRecaptchaToken';
|
|
|
|
// Kody błędów popularnych marek
|
|
const ERROR_CODES: Record<string, Record<string, string>> = {
|
|
'Gree': {
|
|
'E1': 'Błąd czujnika temperatury powietrza wewnętrznego',
|
|
'E2': 'Błąd czujnika temperatury wymiennika wewnętrznego',
|
|
'E3': 'Błąd czujnika temperatury zewnętrznej',
|
|
'E4': 'Wysokie ciśnienie — możliwy problem ze sprężarką lub skraplaczem',
|
|
'E5': 'Niskie ciśnienie — możliwy brak czynnika chłodniczego',
|
|
'E6': 'Błąd komunikacji między jednostkami',
|
|
'F1': 'Awaria czujnika temperatury — wymagana wymiana czujnika',
|
|
'H6': 'Blokada silnika wentylatora wewnętrznego',
|
|
},
|
|
'LG': {
|
|
'CH01': 'Błąd czujnika temperatury powietrza wewnętrznego',
|
|
'CH02': 'Błąd czujnika wymiennika wewnętrznego',
|
|
'CH03': 'Błąd czujnika wymiennika zewnętrznego',
|
|
'CH05': 'Błąd komunikacji — sprawdź okablowanie',
|
|
'CH07': 'Awaria wentylatora zewnętrznego',
|
|
'CH10': 'Błąd czujnika prądu sprężarki',
|
|
'CH38': 'Błąd modułu WiFi',
|
|
'C1': 'Problem z drenażem — sprawdź odpływ skroplin',
|
|
},
|
|
'Daikin': {
|
|
'A1': 'Awaria PCB — płyta główna jednostki wewnętrznej',
|
|
'A5': 'Przegrzanie — wymagane czyszczenie lub sprawdzenie czynnika',
|
|
'C4': 'Błąd czujnika wymiennika wewnętrznego',
|
|
'E1': 'Awaria PCB jednostki zewnętrznej',
|
|
'E3': 'Wyłącznik wysokiego ciśnienia',
|
|
'E4': 'Wyłącznik niskiego ciśnienia',
|
|
'F3': 'Niskie ciśnienie oleju sprężarki',
|
|
'U4': 'Błąd komunikacji wewnętrzna-zewnętrzna',
|
|
},
|
|
'Samsung': {
|
|
'E1': 'Błąd czujnika temperatury powietrza',
|
|
'E2': 'Błąd czujnika wymiennika ciepła',
|
|
'E3': 'Błąd silnika wentylatora',
|
|
'E4': 'Wyłącznik wysokiego ciśnienia',
|
|
'E6': 'Błąd komunikacji',
|
|
'F1': 'Awaria czujnika — wymagana diagnostyka',
|
|
'C4': 'Błąd czujnika temperatury zewnętrznej',
|
|
},
|
|
'Haier': {
|
|
'E1': 'Błąd czujnika temperatury wewnętrznej',
|
|
'E2': 'Błąd czujnika wymiennika wewnętrznego',
|
|
'E3': 'Błąd czujnika temperatury zewnętrznej',
|
|
'E4': 'Wyłącznik wysokiego ciśnienia',
|
|
'E5': 'Wyłącznik niskiego ciśnienia — sprawdź poziom czynnika',
|
|
'E6': 'Błąd komunikacji',
|
|
'F1': 'Awaria czujnika — wymiana',
|
|
},
|
|
'Rotenso': {
|
|
'E1': 'Błąd czujnika temperatury wewnętrznej',
|
|
'E2': 'Błąd wymiennika wewnętrznego',
|
|
'E3': 'Błąd czujnika zewnętrznego',
|
|
'E5': 'Niskie ciśnienie — sprawdź czynnik chłodniczy',
|
|
'E6': 'Błąd komunikacji między jednostkami',
|
|
'P1': 'Ochrona przed zamrożeniem',
|
|
'P4': 'Wyłącznik wysokiego ciśnienia',
|
|
},
|
|
'Mitsubishi Electric': {
|
|
'E1': 'Błąd czujnika wewnętrznego',
|
|
'E2': 'Błąd czujnika wymiennika',
|
|
'E4': 'Błąd czujnika zewnętrznego',
|
|
'E6': 'Problem z wentylatorem zewnętrznym',
|
|
'P1': 'Błąd czujnika powietrza zewnętrznego',
|
|
'P4': 'Wyłącznik wysokiego ciśnienia',
|
|
'P8': 'Przegrzanie sprężarki',
|
|
'U1': 'Błąd napięcia zasilania',
|
|
},
|
|
'Toshiba': {
|
|
'E01': 'Błąd czujnika temperatury wewnętrznej',
|
|
'E02': 'Błąd czujnika wymiennika wewnętrznego',
|
|
'E03': 'Błąd czujnika zewnętrznego',
|
|
'E04': 'Wyłącznik wysokiego ciśnienia',
|
|
'E07': 'Błąd komunikacji',
|
|
'E08': 'Awaria wentylatora zewnętrznego',
|
|
},
|
|
};
|
|
|
|
const BRANDS = ['Gree', 'Rotenso', 'Haier', 'LG', 'Samsung', 'Daikin', 'Mitsubishi Electric', 'Toshiba', 'Aux', 'Fuji Electric', 'Inna marka'];
|
|
const PROBLEM_TYPES = [
|
|
'Nie chłodzi / nie grzeje',
|
|
'Wyświetla kod błędu',
|
|
'Głośna praca / wibracje',
|
|
'Wyciek wody',
|
|
'Brzydki zapach',
|
|
'Nie włącza się',
|
|
'Pilot nie działa',
|
|
'Zamarza wymiennik',
|
|
'Inne',
|
|
];
|
|
|
|
export function AwariaClient() {
|
|
const [step, setStep] = useState<'form' | 'success'>('form');
|
|
const [isUrgent, setIsUrgent] = useState(false);
|
|
const [brand, setBrand] = useState('');
|
|
const [model, setModel] = useState('');
|
|
const [errorCode, setErrorCode] = useState('');
|
|
const [problemType, setProblemType] = useState('');
|
|
const [description, setDescription] = useState('');
|
|
const [lastService, setLastService] = useState('');
|
|
const [warranty, setWarranty] = useState('');
|
|
const [name, setName] = useState('');
|
|
const [phone, setPhone] = useState('');
|
|
const [city, setCity] = useState('');
|
|
const [consent, setConsent] = useState(false);
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
|
|
const errorHint = brand && errorCode && ERROR_CODES[brand]?.[errorCode.toUpperCase()];
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (!consent) {
|
|
alert('Proszę zaznaczyć zgodę na przetwarzanie danych.');
|
|
return;
|
|
}
|
|
setIsSubmitting(true);
|
|
|
|
const recaptchaToken = await getRecaptchaToken('submit');
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const formDataObj: any = {
|
|
isUrgent,
|
|
brand,
|
|
model,
|
|
errorCode,
|
|
problemType,
|
|
description,
|
|
warranty,
|
|
lastService,
|
|
name,
|
|
phone,
|
|
city,
|
|
preferredDate: '',
|
|
preferredTime: '',
|
|
};
|
|
if (recaptchaToken) formDataObj.recaptchaToken = recaptchaToken;
|
|
|
|
await submitAwariaForm(formDataObj);
|
|
|
|
setIsSubmitting(false);
|
|
setStep('success');
|
|
};
|
|
|
|
if (step === 'success') {
|
|
return (
|
|
<main className="bg-slate-50 min-h-screen flex items-center justify-center py-16">
|
|
<div className="max-w-md mx-auto px-4 w-full">
|
|
<div className="bg-green-50 text-green-800 p-8 rounded-2xl border border-green-200 flex flex-col items-center text-center">
|
|
<CheckCircle2 className="w-16 h-16 text-green-500 mb-4" />
|
|
<h3 className="text-2xl font-bold font-heading mb-2">Zgłoszenie przyjęte!</h3>
|
|
<p className="text-green-700 mb-2">
|
|
{isUrgent
|
|
? '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.'}
|
|
</p>
|
|
<p className="text-sm text-green-700/80 mb-6">
|
|
Jeśli sprawa jest pilna — zadzwoń bezpośrednio: {companyDetails.phone}
|
|
</p>
|
|
<button
|
|
onClick={() => setStep('form')}
|
|
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>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<main className="bg-slate-50 min-h-screen py-12">
|
|
<div className="max-w-2xl mx-auto px-4 sm:px-6">
|
|
<div className="text-center mb-8">
|
|
<div className="size-14 bg-red-100 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
|
<Wrench className="size-7 text-red-600" />
|
|
</div>
|
|
<h1 className="text-3xl font-extrabold text-primary font-heading mb-2">
|
|
Zgłoś awarię / serwis
|
|
</h1>
|
|
<p className="text-text-muted">Wypełnij formularz — oddzwonimy i ustalimy termin wizyty</p>
|
|
</div>
|
|
|
|
<div
|
|
onClick={() => setIsUrgent(!isUrgent)}
|
|
className={`flex items-center gap-4 p-4 rounded-2xl border-2 cursor-pointer mb-6 transition-all ${
|
|
isUrgent ? 'border-red-400 bg-red-50' : 'border-border bg-white hover:border-red-300'
|
|
}`}
|
|
>
|
|
<div className={`size-10 rounded-xl flex items-center justify-center shrink-0 ${isUrgent ? 'bg-red-500' : 'bg-slate-100'}`}>
|
|
<AlertTriangle className={`size-5 ${isUrgent ? 'text-white' : 'text-text-muted'}`} />
|
|
</div>
|
|
<div className="flex-1">
|
|
<p className={`font-bold text-sm ${isUrgent ? 'text-red-700' : 'text-primary'}`}>
|
|
{isUrgent ? '🚨 ZGŁOSZENIE PILNE — kontakt w ciągu 2 godzin' : 'Oznacz jako PILNE'}
|
|
</p>
|
|
<p className="text-xs text-text-muted">Np. upał, brak chłodzenia, awaria krytyczna</p>
|
|
</div>
|
|
<div className={`size-6 rounded-full border-2 flex items-center justify-center shrink-0 ${isUrgent ? 'border-red-500 bg-red-500' : 'border-slate-300'}`}>
|
|
{isUrgent && <Check className="size-3.5 text-white" />}
|
|
</div>
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit} className="flex flex-col gap-5">
|
|
|
|
<div className="bg-white rounded-2xl border border-border p-6">
|
|
<h2 className="text-base font-bold text-primary font-heading mb-4 flex items-center gap-2">
|
|
<Zap className="size-4 text-accent" /> Urządzenie
|
|
</h2>
|
|
<div className="flex flex-col gap-4">
|
|
<div>
|
|
<label className="text-xs font-medium text-primary mb-1 block">Marka *</label>
|
|
<select
|
|
required value={brand} onChange={e => { setBrand(e.target.value); setErrorCode(''); }}
|
|
className="w-full h-[42px] rounded-xl px-3 py-2.5 text-sm border border-border focus:border-accent outline-none bg-white"
|
|
>
|
|
<option value="">Wybierz markę...</option>
|
|
{BRANDS.map(b => <option key={b} value={b}>{b}</option>)}
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="text-xs font-medium text-primary mb-1 block">Model (opcjonalnie)</label>
|
|
<input
|
|
value={model} onChange={e => setModel(e.target.value)}
|
|
className="w-full h-[42px] rounded-xl px-3 py-2.5 text-sm border border-border focus:border-accent outline-none"
|
|
placeholder="Np. Gree Pular, LG Artcool 3,5 kW..."
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="text-xs font-medium text-primary mb-1 block">Na gwarancji?</label>
|
|
<select
|
|
value={warranty} onChange={e => setWarranty(e.target.value)}
|
|
className="w-full h-[42px] rounded-xl px-3 py-2.5 text-sm border border-border focus:border-accent outline-none bg-white"
|
|
>
|
|
<option value="">Nie wiem</option>
|
|
<option value="tak">Tak</option>
|
|
<option value="nie">Nie</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-primary mb-1 block">Ostatni serwis</label>
|
|
<select
|
|
value={lastService} onChange={e => setLastService(e.target.value)}
|
|
className="w-full h-[42px] rounded-xl px-3 py-2.5 text-sm border border-border focus:border-accent outline-none bg-white"
|
|
>
|
|
<option value="">Nie pamiętam</option>
|
|
<option value="<1rok">Mniej niż rok temu</option>
|
|
<option value="1-2lata">1-2 lata temu</option>
|
|
<option value="2-5lat">2-5 lat temu</option>
|
|
<option value=">5lat">Ponad 5 lat temu</option>
|
|
<option value="nigdy">Nigdy nie był serwisowany</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-white rounded-2xl border border-border p-6">
|
|
<h2 className="text-base font-bold text-primary font-heading mb-4 flex items-center gap-2">
|
|
<AlertTriangle className="size-4 text-accent" /> Opis problemu
|
|
</h2>
|
|
<div className="flex flex-col gap-4">
|
|
<div>
|
|
<label className="text-xs font-medium text-primary mb-2 block">Rodzaj problemu *</label>
|
|
<div className="flex flex-wrap gap-2">
|
|
{PROBLEM_TYPES.map(p => (
|
|
<button
|
|
key={p} type="button"
|
|
onClick={() => setProblemType(p)}
|
|
className={`px-3 py-1.5 rounded-xl text-xs font-semibold border-2 transition-all ${
|
|
problemType === p ? 'border-accent bg-accent text-white' : 'border-border bg-white text-primary hover:border-accent'
|
|
}`}
|
|
>
|
|
{p}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label className="text-xs font-medium text-primary mb-1 block">
|
|
Kod błędu z wyświetlacza (opcjonalnie)
|
|
</label>
|
|
<input
|
|
value={errorCode}
|
|
onChange={e => setErrorCode(e.target.value)}
|
|
className="w-full h-[42px] rounded-xl px-3 py-2.5 text-sm border border-border focus:border-accent outline-none"
|
|
placeholder="Np. E1, F3, CH05..."
|
|
/>
|
|
{errorHint && (
|
|
<div className="mt-2 p-3 bg-blue-50 border border-blue-200 rounded-xl text-xs text-blue-700">
|
|
<span className="font-bold">Kod {errorCode.toUpperCase()}:</span> {errorHint}
|
|
</div>
|
|
)}
|
|
{errorCode && !errorHint && brand && ERROR_CODES[brand] && (
|
|
<p className="mt-1 text-xs text-text-muted">Kodu nie rozpoznano — serwisant zdiagnozuje na miejscu.</p>
|
|
)}
|
|
</div>
|
|
|
|
<div>
|
|
<label className="text-xs font-medium text-primary mb-1 block">Szczegółowy opis</label>
|
|
<textarea
|
|
value={description} onChange={e => setDescription(e.target.value)}
|
|
rows={3}
|
|
className="w-full rounded-xl px-3 py-2.5 text-sm border border-border focus:border-accent outline-none resize-none"
|
|
placeholder="Opisz kiedy się pojawił problem, jak często występuje, czy jest jakiś charakterystyczny dźwięk..."
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div className="bg-white rounded-2xl border border-border p-6">
|
|
<h2 className="text-base font-bold text-primary font-heading mb-4 flex items-center gap-2">
|
|
<Phone className="size-4 text-accent" /> Dane kontaktowe
|
|
</h2>
|
|
<div className="flex flex-col gap-4">
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
<div>
|
|
<label className="text-xs font-medium text-primary mb-1 block">Imię i nazwisko *</label>
|
|
<input
|
|
required value={name} onChange={e => setName(e.target.value)}
|
|
className="w-full h-[42px] rounded-xl px-3 py-2.5 text-sm border border-border focus:border-accent outline-none"
|
|
placeholder="Jan Kowalski"
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-primary mb-1 block">Telefon *</label>
|
|
<input
|
|
required type="tel" value={phone} onChange={e => setPhone(e.target.value)}
|
|
className="w-full h-[42px] rounded-xl px-3 py-2.5 text-sm border border-border focus:border-accent outline-none"
|
|
placeholder="+48 600 000 000"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label className="text-xs font-medium text-primary mb-1 block">Miasto *</label>
|
|
<select
|
|
required value={city} onChange={e => setCity(e.target.value)}
|
|
className="w-full h-[42px] rounded-xl px-3 py-2.5 text-sm border border-border focus:border-accent outline-none bg-white"
|
|
>
|
|
<option value="">Wybierz miasto...</option>
|
|
{['Wrocław', 'Legnica', 'Wałbrzych', 'Jelenia Góra', 'Lubin', 'Głogów', 'Świdnica', 'Dzierżoniów', 'Oleśnica', 'Oława', 'Trzebnica', 'Środa Śląska', 'Siechnice', 'Inne'].map(c => (
|
|
<option key={c} value={c}>{c}</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
className="flex items-start gap-3 cursor-pointer"
|
|
onClick={() => setConsent(!consent)}
|
|
>
|
|
<div className={`size-5 rounded border-2 shrink-0 mt-0.5 flex items-center justify-center transition-colors ${
|
|
consent ? 'bg-accent border-accent' : 'bg-white border-slate-300'
|
|
}`}>
|
|
{consent && <Check className="size-3 text-white" />}
|
|
</div>
|
|
<span className="text-xs text-text-muted leading-relaxed">
|
|
Wyrażam zgodę na przetwarzanie danych osobowych podanych w formularzu
|
|
w celu obsługi zgłoszenia serwisowego przez firmę ThermCool. *
|
|
</span>
|
|
</div>
|
|
|
|
<button
|
|
type="submit"
|
|
disabled={isSubmitting || !brand || !problemType || !name || !phone || !city || !consent}
|
|
className={`flex items-center justify-center gap-2 py-4 px-6 rounded-2xl font-bold text-base transition-all ${
|
|
isUrgent
|
|
? 'bg-red-500 hover:bg-red-600 text-white'
|
|
: 'bg-accent hover:bg-accent/90 text-white'
|
|
} disabled:opacity-50 disabled:cursor-not-allowed`}
|
|
>
|
|
{isSubmitting ? 'Wysyłanie...' : isUrgent ? '🚨 Wyślij PILNE zgłoszenie' : 'Wyślij zgłoszenie serwisowe'}
|
|
</button>
|
|
|
|
<p className="text-xs text-text-muted text-center">
|
|
{isUrgent ? 'Odpowiadamy na pilne zgłoszenia w ciągu 2 godzin w godzinach pracy.' : 'Skontaktujemy się w ciągu 24 godzin.'}
|
|
{' '}Możesz też zadzwonić bezpośrednio: <a href={`tel:${companyDetails.phoneRaw}`} className="text-accent font-semibold">{companyDetails.phone}</a>
|
|
</p>
|
|
</form>
|
|
</div>
|
|
</main>
|
|
);
|
|
}
|