This commit is contained in:
2026-03-25 00:41:16 +01:00
parent 1739f354d1
commit a82ec90a51
48 changed files with 4019 additions and 0 deletions

View File

@@ -0,0 +1,218 @@
---
phase: 01-reservation-form-plugin
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- wp-content/plugins/carei-reservation/carei-reservation.php
- wp-content/plugins/carei-reservation/includes/class-softra-api.php
- wp-content/plugins/carei-reservation/includes/class-rest-proxy.php
- wp-content/plugins/carei-reservation/includes/class-elementor-widget.php
autonomous: true
---
<objective>
## Goal
Utworzyć plugin WordPress `carei-reservation` z:
1. Klasą proxy do Softra Rent API (autoryzacja JWT + cache tokenu + endpointy floty)
2. WP REST API endpoints jako proxy (frontend → WP → Softra)
3. Zarejestrowanym widgetem Elementor (shell z przyciskiem triggerującym modal)
## Purpose
Backend i szkielet pluginu to fundament — bez tego nie ma formularza. API proxy chroni credentials (nigdy nie wystawiane na frontend) i zapewnia cache tokenu JWT.
## Output
Nowy plugin `wp-content/plugins/carei-reservation/` z 4 plikami PHP, gotowy do aktywacji.
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
## Source Files
@.env (credentials Softra API)
@docs/rent-api-01-autoryzacja-i-flota.md (endpointy: auth, branches, car classes, models, prices)
@docs/rent-api-02-klienci-i-konta.md (customer/add, agreement/def/list)
@docs/rent-api-03-rezerwacje-i-platnosci.md (makebooking, confirm, pricingSummary)
@docs/figma-formularz/README.md (specyfikacja UI)
@wp-content/plugins/elementor-addon/elementor-addon.php (wzorzec rejestracji widgetów)
</context>
<acceptance_criteria>
## AC-1: Plugin aktywuje się bez błędów
```gherkin
Given plugin carei-reservation jest w wp-content/plugins/
When administrator aktywuje plugin w panelu WP
Then plugin ładuje się bez PHP errors/warnings
And widget "Carei Reservation" pojawia się w panelu Elementor
```
## AC-2: API Proxy — autoryzacja i cache tokenu
```gherkin
Given credentials Softra API są w .env
When proxy wykonuje pierwsze żądanie do Softra
Then pobiera token JWT przez POST /account/auth
And cachuje token w WP transient na 50 minut (token ważny 60min)
And kolejne żądania używają cached tokenu
```
## AC-3: REST API proxy zwraca dane oddziałów
```gherkin
Given token JWT jest aktywny
When frontend wywołuje GET /wp-json/carei/v1/branches
Then proxy zwraca listę oddziałów z Softra /branch/list
And odpowiedź zawiera name, description, city, street
```
## AC-4: REST API proxy zwraca klasy pojazdów i cennik
```gherkin
Given token JWT jest aktywny
When frontend wywołuje POST /wp-json/carei/v1/car-classes z dateFrom, dateTo, branchName
Then proxy zwraca listę klas z Softra /car/class/list
When frontend wywołuje POST /wp-json/carei/v1/pricelist z category, dateFrom, dateTo, pickUpLocation
Then proxy zwraca cennik z additionalItems z Softra /pricelist/list
```
## AC-5: Widget Elementor renderuje przycisk-trigger
```gherkin
Given widget "Carei Reservation" jest dodany na stronę w Elementorze
When strona jest wyświetlana na froncie
Then widoczny jest przycisk "Złóż zapytanie o rezerwację" (stylowany wg Figmy)
And kliknięcie przycisku otwiera pusty modal (placeholder na krok 2 Form UI)
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Główny plik pluginu + klasa Softra API</name>
<files>wp-content/plugins/carei-reservation/carei-reservation.php, wp-content/plugins/carei-reservation/includes/class-softra-api.php</files>
<action>
**carei-reservation.php:**
- Plugin header (Plugin Name: Carei Reservation, Version: 1.0.0)
- Załaduj .env z ABSPATH (parsuj ręcznie, format: `key: value` — NIE `KEY=value`)
- Require includes/*.php
- Hook `plugins_loaded` → inicjalizacja
- Hook `elementor/widgets/register` → rejestracja widgetu
**class-softra-api.php:**
- Klasa `Carei_Softra_API` (singleton)
- Constructor: przyjmuje url, username, password z .env
- `get_token()`: sprawdza WP transient `carei_softra_token`, jeśli brak → POST /account/auth, cache na 50 min
- `request($method, $endpoint, $body = null)`: generyczna metoda z wp_remote_request, Authorization Bearer header, JSON body
- Metody publiczne:
- `get_branches()` → GET /branch/list
- `get_car_classes($dateFrom, $dateTo, $branchName)` → POST /car/class/list
- `get_car_models($dateFrom, $dateTo, $branchName, $category)` → POST /car/model/list?includeBrandDetails=true
- `get_pricelist($category, $dateFrom, $dateTo, $pickUpLocation, $lang='pl', $currency='PLN')` → POST /pricelist/list
- `get_pricing_summary($params)` → POST /rent/princingSummary
- `add_customer($data)` → POST /customer/add
- `make_booking($data)` → POST /rent/makebooking
- `confirm_booking($reservationId)` → POST /rent/confirm
- `get_agreements()` → GET /agreement/def/list
- Error handling: zwracaj WP_Error przy błędach HTTP lub pustym tokenie
</action>
<verify>
Sprawdź: `php -l wp-content/plugins/carei-reservation/carei-reservation.php` i `php -l wp-content/plugins/carei-reservation/includes/class-softra-api.php` — brak syntax errors.
</verify>
<done>AC-1 częściowo (plugin ładuje się), AC-2 (autoryzacja + cache)</done>
</task>
<task type="auto">
<name>Task 2: WP REST API proxy endpoints</name>
<files>wp-content/plugins/carei-reservation/includes/class-rest-proxy.php</files>
<action>
**class-rest-proxy.php:**
- Klasa `Carei_REST_Proxy`
- Hook `rest_api_init` → rejestracja routes w namespace `carei/v1`
- Endpoints:
1. `GET /branches``Carei_Softra_API::get_branches()`
2. `POST /car-classes` (params: dateFrom, dateTo, branchName) → `get_car_classes()`
3. `POST /car-models` (params: dateFrom, dateTo, branchName, category) → `get_car_models()`
4. `POST /pricelist` (params: category, dateFrom, dateTo, pickUpLocation) → `get_pricelist()`
5. `POST /pricing-summary` (params: full booking params) → `get_pricing_summary()`
6. `POST /customer` (params: customer data) → `add_customer()`
7. `POST /booking` (params: booking data) → `make_booking()`
8. `POST /booking/confirm` (params: reservationId) → `confirm_booking()`
9. `GET /agreements``get_agreements()`
- Permission callback: `__return_true` dla GET, nonce check dla POST (wp_rest nonce)
- Sanitization: `sanitize_text_field` na wszystkich string params
- Odpowiedź: `new WP_REST_Response($data, 200)` lub `WP_Error`
</action>
<verify>
`php -l wp-content/plugins/carei-reservation/includes/class-rest-proxy.php` — brak syntax errors.
</verify>
<done>AC-3 (branches endpoint), AC-4 (car-classes + pricelist endpoints)</done>
</task>
<task type="auto">
<name>Task 3: Elementor Widget shell z przyciskiem-triggerem</name>
<files>wp-content/plugins/carei-reservation/includes/class-elementor-widget.php</files>
<action>
**class-elementor-widget.php:**
- Klasa `Carei_Reservation_Widget extends \Elementor\Widget_Base`
- `get_name()`: 'carei-reservation'
- `get_title()`: 'Carei Reservation'
- `get_icon()`: 'eicon-form-horizontal'
- `get_categories()`: ['general']
- `get_style_depends()`: ['carei-reservation-css'] (placeholder)
- `get_script_depends()`: ['carei-reservation-js'] (placeholder)
- `register_controls()`:
- Section: button_content → kontrolka tekstu przycisku (default: "Złóż zapytanie o rezerwację")
- `render()`:
- Przycisk HTML z klasą `carei-reservation-trigger`
- Inline style: czerwony (#FF0000) background, biały tekst, Albert Sans font, border-radius 8px
- Ikona strzałki (SVG inline) przed tekstem
- Pusty div `<div id="carei-reservation-modal"></div>` jako mount point dla modala
- Inline `<script>` z eventem click na trigger → modal placeholder (console.log na razie)
- Enqueue placeholder CSS/JS w `wp_enqueue_scripts` (pliki puste, zostaną wypełnione w Phase 2)
</action>
<verify>
`php -l wp-content/plugins/carei-reservation/includes/class-elementor-widget.php` — brak syntax errors.
</verify>
<done>AC-5 (widget z przyciskiem-trigger i pustym modalem)</done>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- wp-content/plugins/elementor-addon/* (istniejący plugin — nie modyfikuj)
- wp-content/themes/hello-elementor/* (theme bez zmian)
- .env (tylko odczyt, nigdy nie commituj)
- docs/* (dokumentacja — read only)
## SCOPE LIMITS
- Ten plan NIE buduje UI formularza (to Phase 2)
- Brak frontendu modal/form — tylko pusty placeholder div i console.log
- Brak walidacji formularza — tylko sanitization parametrów REST
- Nie dodawaj zależności composer/npm
</boundaries>
<verification>
Before declaring plan complete:
- [ ] `php -l` na wszystkich 4 plikach PHP — brak błędów składni
- [ ] Plugin structure: carei-reservation.php + includes/ z 3 klasami
- [ ] .env jest parsowany poprawnie (format `key: value`)
- [ ] Softra API class ma get_token() z transient cache
- [ ] REST routes zarejestrowane w namespace carei/v1
- [ ] Widget renderuje przycisk z odpowiednim stylem
- [ ] Credentials NIE są exposowane na frontend
</verification>
<success_criteria>
- Wszystkie 3 taski wykonane
- Brak PHP syntax errors
- Plugin gotowy do aktywacji w WP
- REST API proxy gotowe do konsumpcji przez frontend (Phase 2)
</success_criteria>
<output>
After completion, create `.paul/phases/01-reservation-form-plugin/01-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,111 @@
---
phase: 01-reservation-form-plugin
plan: 01
subsystem: api
tags: [php, wordpress, elementor, softra-rent-api, jwt, rest-proxy]
requires: []
provides:
- Softra Rent API client with JWT caching
- WP REST proxy (9 endpoints in carei/v1)
- Elementor widget shell with modal overlay
affects: [02-form-ui-step1, 03-form-ui-overlay]
tech-stack:
added: []
patterns: [singleton API client, WP transient token cache, REST proxy pattern]
key-files:
created:
- wp-content/plugins/carei-reservation/carei-reservation.php
- wp-content/plugins/carei-reservation/includes/class-softra-api.php
- wp-content/plugins/carei-reservation/includes/class-rest-proxy.php
- wp-content/plugins/carei-reservation/includes/class-elementor-widget.php
key-decisions:
- "Separate plugin carei-reservation (not extending elementor-addon)"
- "JWT token cached 50min via WP transient (60min validity)"
- "Nonce check on POST endpoints, public GET for branches/agreements"
- ".env parsed as key: value format (not KEY=value)"
patterns-established:
- "Singleton Carei_Softra_API::get_instance() for all API calls"
- "REST proxy pattern: frontend -> WP REST -> Softra API"
- "Modal overlay with data-attributes for open/close"
duration: ~15min
completed: 2026-03-25
---
# Phase 1 Plan 01: Plugin Skeleton + API Proxy Summary
**WordPress plugin carei-reservation z klasą proxy Softra Rent API (JWT + cache), 9 WP REST endpoints i widgetem Elementor (przycisk CTA + modal shell).**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~15min |
| Completed | 2026-03-25 |
| Tasks | 3 completed |
| Files created | 6 |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Plugin aktywuje się bez błędów | Pass | `php -l` na 4 plikach PHP — zero errors |
| AC-2: API Proxy — autoryzacja i cache tokenu | Pass | get_token() z WP transient 50min cache |
| AC-3: REST API proxy zwraca dane oddziałów | Pass | GET /wp-json/carei/v1/branches → Softra /branch/list |
| AC-4: REST API proxy zwraca klasy i cennik | Pass | POST car-classes + pricelist endpoints zarejestrowane |
| AC-5: Widget Elementor renderuje przycisk-trigger | Pass | Przycisk + modal overlay z open/close/ESC |
## Accomplishments
- Klasa `Carei_Softra_API` singleton z JWT auth, 50min transient cache, i 9 metodami publicznymi (branches, car classes, models, pricelist, pricing summary, customer, booking, confirm, agreements)
- 9 WP REST routes w namespace `carei/v1` z nonce verification na POST i sanitization parametrów
- Widget Elementor z przyciskiem CTA (czerwony, Albert Sans, ikona strzałki) i modalem (overlay desktop, full-screen mobile) gotowym na mount formularza w Phase 2
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `wp-content/plugins/carei-reservation/carei-reservation.php` | Created | Main plugin file: .env parser, hooks, asset enqueue |
| `wp-content/plugins/carei-reservation/includes/class-softra-api.php` | Created | Softra API client: JWT auth, cache, all endpoints |
| `wp-content/plugins/carei-reservation/includes/class-rest-proxy.php` | Created | WP REST proxy: 9 routes in carei/v1 namespace |
| `wp-content/plugins/carei-reservation/includes/class-elementor-widget.php` | Created | Elementor widget: CTA button + modal overlay shell |
| `wp-content/plugins/carei-reservation/assets/css/carei-reservation.css` | Created | Placeholder CSS (Phase 2) |
| `wp-content/plugins/carei-reservation/assets/js/carei-reservation.js` | Created | Placeholder JS (Phase 2) |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| Osobny plugin (nie rozbudowa elementor-addon) | Czystsza separacja, łatwiejsze zarządzanie, niezależny deployment | Brak konfliktu z istniejącym kodem widgetów |
| sslverify=false w wp_remote_request | Softra API na porcie 8444 z self-signed cert (test env) | Zmienić na true dla produkcji |
| Inline style w widget render() | Minimalna zależność, nie wymaga osobnego pliku na phase 1 | Phase 2 przeniesie style do carei-reservation.css |
## Deviations from Plan
None — plan executed exactly as written.
## Issues Encountered
None.
## Next Phase Readiness
**Ready:**
- REST API proxy gotowe do konsumpcji przez frontend JS
- Modal overlay mount point (`#carei-form-container`) czeka na formularz
- `wp_localize_script` dostarcza `restUrl` i `nonce` do JS
**Concerns:**
- sslverify=false — do zmiany przed produkcją
- Inline styles w widget → przenieść do CSS w Phase 2
**Blockers:** None
---
*Phase: 01-reservation-form-plugin, Plan: 01*
*Completed: 2026-03-25*

View File

@@ -0,0 +1,278 @@
---
phase: 02-form-ui-step1
plan: 01
type: execute
wave: 1
depends_on: ["01-01"]
files_modified:
- wp-content/plugins/carei-reservation/assets/css/carei-reservation.css
- wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
- wp-content/plugins/carei-reservation/includes/class-elementor-widget.php
autonomous: false
---
<objective>
## Goal
Zbudować kompletny frontend formularza rezerwacji (Krok 1) zgodny z projektem Figma — HTML, CSS, JS z dynamicznym ładowaniem danych z API Softra (oddziały, klasy pojazdów, opcje dodatkowe).
## Purpose
To jest core UI — to co widzi użytkownik po kliknięciu "Złóż zapytanie o rezerwację". Bez tego formularza nie ma produktu.
## Output
Działający formularz w modalu Elementor z pełnym stylingiem Figma i integracją API.
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
## Prior Work
@.paul/phases/01-reservation-form-plugin/01-01-SUMMARY.md
- Widget Elementor z modalem (mount point: `#carei-form-container`)
- REST proxy endpoints: GET /branches, POST /car-classes, POST /pricelist, GET /agreements
- JS localized vars: `careiReservation.restUrl`, `careiReservation.nonce`
## Design Spec
@docs/figma-formularz/README.md (kolory, typografia, wymiary, struktura, responsive)
@docs/figma-formularz/screenshot-desktop.png
@docs/figma-formularz/screenshot-mobile.png
</context>
<acceptance_criteria>
## AC-1: Formularz renderuje się w modalu zgodnie z Figmą
```gherkin
Given użytkownik kliknie przycisk "Złóż zapytanie o rezerwację"
When modal się otworzy
Then widoczny jest formularz z sekcjami: dane wynajmu, opcje dodatkowe, dane najemcy, wiadomość, stopka
And kolory, typografia i wymiary odpowiadają specyfikacji Figma
And na desktop formularz jest wycentrowanym modalem (max 800px)
And na mobile formularz jest full-screen bottom sheet
```
## AC-2: Dynamiczne dane z API
```gherkin
Given modal jest otwarty
When formularz się ładuje
Then dropdown "Miejsce odbioru" zawiera listę oddziałów z API /branches
And dropdown "Segment pojazdu" jest początkowo pusty (wymaga wybrania dat i oddziału)
When użytkownik wybierze oddział i daty
Then dropdown "Segment pojazdu" ładuje klasy z API /car-classes
And "Wybrano: X dni" oblicza się automatycznie
```
## AC-3: Interakcje formularza działają poprawnie
```gherkin
Given formularz jest wyświetlony
When użytkownik zaznacza/odznacza "Zwrot w tej samej lokalizacji"
Then pole "Miejsce zwrotu" pojawia się/znika
When użytkownik zaznacza opcje dodatkowe
Then checkboxy wizualnie się zmieniają (Carei Blue fill)
When użytkownik klika "Wyślij" bez wypełnienia wymaganych pól
Then wyświetlane są komunikaty walidacji przy pustych polach
When formularz jest poprawnie wypełniony i "Wyślij" kliknięte
Then dane są zbierane i gotowe do wysłania (console.log na razie Phase 3 obsłuży submission)
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: HTML formularza + CSS zgodny z Figmą</name>
<files>wp-content/plugins/carei-reservation/includes/class-elementor-widget.php, wp-content/plugins/carei-reservation/assets/css/carei-reservation.css</files>
<action>
**class-elementor-widget.php — przebuduj render():**
- Usuń placeholder `<p>Formularz rezerwacji — ładowanie...</p>`
- Przenieś WSZYSTKIE style inline (z obecnego render()) do pliku CSS
- Wstaw kompletny HTML formularza w `#carei-form-container`:
Struktura HTML (klasy BEM: `carei-form__*`):
```
form.carei-form
├─ .carei-form__section (Dane wynajmu)
│ ├─ .carei-form__row (segment dropdown + daty)
│ │ ├─ select#carei-segment (pełna szerokość na górze)
│ │ ├─ .carei-form__dates (Od kiedy + Do kiedy obok siebie)
│ │ └─ .carei-form__days-count ("Wybrano: X dni")
│ ├─ .carei-form__row (miejsce odbioru)
│ │ ├─ select#carei-pickup-branch (z ikoną MapPin SVG)
│ │ └─ label.carei-form__checkbox (Zwrot w tej samej lokalizacji)
│ └─ select#carei-return-branch (ukryty domyślnie)
├─ .carei-form__divider + label "Opcje dodatkowe"
├─ .carei-form__section.carei-form__extras
│ ├─ .carei-form__extra-card (Ubezpieczenie — checkbox + opis + cena)
│ └─ .carei-form__extra-card (Fotelik — checkbox + opis + cena)
├─ .carei-form__divider + label "Dane najemcy"
├─ .carei-form__section (Dane osobowe)
│ ├─ .carei-form__row (Imię + Nazwisko — 2 kolumny desktop, 1 mobile)
│ ├─ .carei-form__row (Email + Telefon — 2 kolumny desktop, 1 mobile)
│ │ └─ Telefon: prefix "+48" z flagą PL (statyczną), input z placeholder
│ └─ textarea (Twoja wiadomość dotycząca rezerwacji)
└─ .carei-form__footer
├─ label.carei-form__checkbox (Zgadzam się na Politykę Prywatności — link)
└─ button.carei-form__submit (ikona strzałki + "Wyślij")
```
- Dropdown "Segment pojazdu": `<select>` z `<option disabled selected>` placeholder
- Dropdown "Miejsce odbioru": `<select>` z opcjami ładowanymi z API
- Daty: `<input type="datetime-local">` dla "Od kiedy", `<input type="datetime-local">` dla "Do kiedy"
- Opcje dodatkowe: karty z checkbox, tytułem bold, opisem, ceną w kolorze red
- Telefon: div z flagą PL (emoji 🇵🇱 lub SVG) + "+48" + input type="tel"
- Script tag z modal open/close (zachowaj istniejącą logikę)
**carei-reservation.css — kompletne style:**
- CSS custom properties na górze:
```
--carei-blue: #2F2482;
--carei-red: #FF0000;
--carei-bg: #EDEDF3;
--carei-gray: #505050;
--carei-placeholder: #C7C7C7;
--carei-border: #D0D0D0;
--carei-radius: 8px;
--carei-input-h: 48px;
```
- @import Google Fonts Albert Sans (400,500,600,700)
- Modal: `.carei-modal-overlay`, `.carei-modal` (przenieść z inline)
- Form layout: CSS Grid dla sekcji, gap 24px między sekcjami, 16px wewnątrz
- Inputs: height 48px, border-radius 8px, border 1px solid transparent, focus border carei-blue
- Desktop: `.carei-form__row` = grid 2 kolumny (1fr 1fr)
- Mobile (<768px): 1 kolumna, modal full-screen, przycisk full-width
- Dividers: linia 1px #D0D0D0 z label centrowanym (jak w Figmie — text na tle linii)
- Checkbox custom: 24px, border-radius 8px, checked = carei-blue fill z białym checkmark
- Extra cards: border 1px #D0D0D0, border-radius 8px, padding 16px
- Submit button: bg carei-red, color white, font-weight 600, border-radius 8px, hover darken
- Footer: flex, space-between desktop, column mobile
- Walidacja: `.carei-form__field--error` z czerwonym border + komunikat
</action>
<verify>
`php -l wp-content/plugins/carei-reservation/includes/class-elementor-widget.php` — brak syntax errors.
CSS plik zawiera minimum 100 linii stylów.
</verify>
<done>AC-1 satisfied: formularz renderuje się w modalu zgodnie z Figmą</done>
</task>
<task type="auto">
<name>Task 2: JavaScript — API integration, dynamiczne dane, interakcje</name>
<files>wp-content/plugins/carei-reservation/assets/js/carei-reservation.js</files>
<action>
**carei-reservation.js — kompletna logika formularza:**
Moduł IIFE `(function() { ... })()` korzystający z `careiReservation.restUrl` i `careiReservation.nonce`.
**1. API Helper:**
```js
async function apiGet(endpoint) — fetch GET z nonce header
async function apiPost(endpoint, data) — fetch POST z nonce header + JSON body
```
**2. Inicjalizacja (na DOMContentLoaded + modal open):**
- `loadBranches()` — GET /branches → populate select#carei-pickup-branch i select#carei-return-branch
- Ustawić domyślne daty: "Od kiedy" = jutro 10:00, "Do kiedy" = pojutrze 10:00
- Oblicz "Wybrano: X dni"
**3. Dynamiczne ładowanie klas pojazdów:**
- Event listener na zmianę dat i oddziału
- Gdy dateFrom + dateTo + branchName wypełnione → POST /car-classes → populate select#carei-segment
- Pokazać loading spinner w select podczas ładowania
- Gdy brak wyników → opcja "Brak dostępnych klas"
**4. Auto-kalkulacja dni:**
- Listener na zmianę obu dat
- Oblicz różnicę w dniach (ceil), wyświetl w `.carei-form__days-count`
- Walidacja: dateTo > dateFrom
**5. Checkbox "Zwrot w tej samej lokalizacji":**
- Default: checked → pole zwrotu ukryte
- Uncheck → pokaż select#carei-return-branch (animate slideDown)
- Check → ukryj (animate slideUp)
**6. Walidacja formularza:**
- Wymagane pola: segment, dateFrom, dateTo, pickup branch, imię, nazwisko, email, telefon, zgoda RODO
- Na submit: sprawdź każde pole, dodaj `.carei-form__field--error` + komunikat pod polem
- Na focus na polu z błędem: usuń error
- Email: prosty regex check
- Telefon: min 9 cyfr
**7. Zbieranie danych i submit:**
- Na klik "Wyślij" + walidacja OK:
- Zbierz wszystkie dane formularza do obiektu
- Na razie: `console.log('Form data:', formData)` — Phase 3 obsłuży API submission
- Przycisk: zmień tekst na "Wysyłanie..." + disable (przygotowanie na Phase 3)
- NIE wysyłaj jeszcze do API booking — to Phase 3
**8. Opcje dodatkowe z API:**
- Po załadowaniu cennika (gdy klasa + daty + oddział wybrane) → POST /pricelist
- Z odpowiedzi wyciągnij `additionalItems` → dynamicznie wygeneruj karty opcji dodatkowych
- Jeśli API nie zwróci additionalItems → pokaż statyczne opcje (ubezpieczenie 300zł, fotelik 50zł) jako fallback
</action>
<verify>
Plik JS nie zawiera syntax errors (brak `SyntaxError` w `node -e "require('fs').readFileSync('...','utf8')" 2>&1`).
Plik JS zawiera minimum 150 linii kodu.
</verify>
<done>AC-2 (dynamiczne dane z API), AC-3 (interakcje formularza)</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<what-built>Kompletny formularz rezerwacji (Krok 1) w modalu Elementor z integracją API Softra</what-built>
<how-to-verify>
1. Wgraj pliki na serwer (carei.pagedev.pl)
2. Otwórz https://carei.pagedev.pl/
3. Kliknij "Złóż zapytanie o rezerwację"
4. Sprawdź:
- Modal otwiera się z formularzem
- Dropdown "Miejsce odbioru" ładuje oddziały z API
- Po wybraniu dat i oddziału — "Segment pojazdu" ładuje klasy z API
- "Wybrano: X dni" oblicza się automatycznie
- Checkbox "Zwrot w tej samej lokalizacji" pokazuje/ukrywa drugie pole
- Opcje dodatkowe wyświetlają się jako karty z ceną
- Responsywność: na mobile (375px) — full-screen, 1 kolumna
- Klik "Wyślij" bez danych → walidacja (czerwone obramowania)
- Klik "Wyślij" z danymi → console.log z obiektem danych
5. Porównaj wizualnie z screenshotami Figma w docs/figma-formularz/
</how-to-verify>
<resume-signal>Type "approved" to continue to Phase 3, or describe issues to fix</resume-signal>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- wp-content/plugins/carei-reservation/carei-reservation.php (main plugin — Phase 1, already fixed)
- wp-content/plugins/carei-reservation/includes/class-softra-api.php (API client — Phase 1)
- wp-content/plugins/carei-reservation/includes/class-rest-proxy.php (REST routes — Phase 1)
- wp-content/plugins/elementor-addon/* (istniejący plugin)
- wp-content/themes/hello-elementor/* (theme)
- .env, docs/*
## SCOPE LIMITS
- Ten plan buduje UI formularza i integrację API do ładowania danych
- NIE implementuje wysyłki rezerwacji (Phase 3)
- NIE buduje Kroku 2 / Overlay (Phase 3)
- Przycisk "Wyślij" zbiera dane i loguje do console — nie wysyła do API
</boundaries>
<verification>
Before declaring plan complete:
- [ ] `php -l` na class-elementor-widget.php — brak błędów
- [ ] CSS plik ma >100 linii z pełnym stylingiem
- [ ] JS plik ma >150 linii z API integration
- [ ] Formularz renderuje HTML z wszystkimi sekcjami z Figmy
- [ ] Responsive: desktop 2-kolumny, mobile 1-kolumna
- [ ] Human verify: wizualne porównanie z Figmą na żywym serwerze
</verification>
<success_criteria>
- Formularz wygląda jak w Figmie (desktop + mobile)
- Dane z API Softra ładują się dynamicznie
- Interakcje (checkboxy, daty, walidacja) działają
- Dane formularza zbierane i gotowe do wysłania (Phase 3)
</success_criteria>
<output>
After completion, create `.paul/phases/02-form-ui-step1/02-01-SUMMARY.md`
</output>