# TROPO — Dokumentacja techniczna

## Czym jest TROPO

Polska edukacyjna aplikacja PWA + strona marketingowa. Interaktywne misje z 4 postaciami-przewodnikami (Scout, Blitz, Nova, Echo). Dzieci rozwiązują zagadki, odkrywają świat i zdobywają odznaki.

## Stack technologiczny

| Warstwa | Technologia |
|---|---|
| Framework | Symfony 8.0 (PHP 8.4+) |
| Baza danych | PostgreSQL 16 (Doctrine ORM 3) |
| Szablony | Twig 3 + Twig Components |
| CSS | Tailwind CSS 4 (symfonycasts/tailwind-bundle) |
| JS | Symfony AssetMapper + Stimulus |
| Mailer | Symfony Mailer + Resend |
| Obrazki | LiipImagineBundle (GD) |
| PDF | dompdf 3 |
| CORS | nelmio/cors-bundle |
| Płatności | Przelewy24 (custom P24Service) |
| CAPTCHA | Cloudflare Turnstile (custom TurnstileService) |

## Domeny

- **Dev:** `http://tropo.local`
- **Prod:** `https://tropo.city`
- **PWA:** `https://tropo.app` (osobne repo: `/Users/gladki/projects/tropo-pwa`)

## Struktura katalogów

```
src/
├── Command/                   # CLI: create-admin, import/export-missions, seed-blog
├── Controller/
│   ├── Admin/                 # Panel admina (ROLE_ADMIN)
│   │   ├── BlogController, CommentController, ContactController
│   │   ├── DashboardController, LoginController, MissionController
│   │   ├── SettingsController, TestimonialController, TrackingController
│   │   ├── UserController, WaitlistController
│   ├── Api/
│   │   └── PwaSyncController # POST /api/pwa/sync, GET /api/pwa/sync/{deviceId}, POST /api/pwa/subscription/activate
│   ├── HomeController         # Strona główna
│   ├── CharacterController    # /postacie, /postacie/{slug}
│   ├── BadgeController        # /oznaki
│   ├── StoryController        # /bajki, /bajki/{slug}, PDF, MD
│   ├── BlogController         # /blog, /blog/{slug}
│   ├── ContactController      # /kontakt
│   ├── DownloadController     # /pobierz + waitlist API
│   ├── PricingController      # /cennik, /gra-planszowa
│   ├── PurchaseController     # /kup/{packageId}, /kup/powrot/{id}, /api/p24/notify
│   ├── LegalController        # /polityka-prywatnosci, /regulamin
│   ├── ManifestController     # /manifest
│   ├── SitemapController      # /sitemap.xml
│   └── ComingSoonController   # /wkrotce (maintenance)
├── Entity/
│   ├── Pwa/                   # PwaBadge, PwaChild, PwaCompletedMission, PwaSubscription, PwaPackage
│   ├── Badge, BlogPost, Comment, Contact, Mission, MissionReview
│   ├── PageView, Visitor, Waitlist, Testimonial, User, SiteSettings
├── EventListener/
│   ├── MaintenanceSubscriber  # Redirect do /wkrotce gdy maintenance=on
│   ├── LocaleSubscriber       # ?lang=xx → session _locale
│   ├── TrackingSubscriber     # First-party analytics (kernel.TERMINATE)
│   └── BlameableListener
├── Repository/
├── Service/
│   ├── CharacterService       # 4 postacie (hardcoded PHP)
│   ├── StoryService           # Bajki z sezonami (hardcoded PHP)
│   ├── P24Service             # Przelewy24 API
│   ├── TurnstileService       # Cloudflare Turnstile
│   ├── SiteSettingsService    # Singleton wrapper na SiteSettings
│   ├── MissionService, BlogService, TrackingService
├── templates/
│   ├── email/                 # purchase_confirmation, contact_confirmation, admin_notification, waitlist_confirmation
│   ├── legal/                 # privacy.html.twig, terms.html.twig
│   ├── _macros/               # image.html.twig, pricing_cards.html.twig
│   ├── purchase/              # index, success, failure
```

## Baza danych

### Tabele marketingowe

| Encja | Tabela | Opis |
|---|---|---|
| `User` | `user` | Konta admin (email, roles, password) |
| `Mission` | `mission` | Misje z PWA (id, character, location, difficulty, package_id, steps JSON) |
| `Badge` | `badge` | Odznaki (id, name) |
| `BlogPost` | `blog_post` | Posty na blogu (title, slug, content, is_published) |
| `Comment` | `comment` | Komentarze do postów (author, content, is_approved) |
| `Contact` | `contact` | Wiadomości kontaktowe |
| `MissionReview` | `mission_review` | Recenzje misji |
| `Testimonial` | `testimonial` | Opinie rodziców |
| `Waitlist` | `waitlist` | Lista oczekujących |
| `SiteSettings` | `site_settings` | Singleton: monetization, maintenance, locales |
| `Visitor` | `visitor` | First-party tracking |
| `PageView` | `page_view` | Odsłony stron |

### Tabele PWA sync

| Encja | Tabela | Opis |
|---|---|---|
| `PwaChild` | `pwa_child` | Profile dzieci (device_id + local_id) |
| `PwaCompletedMission` | `pwa_completed_mission` | Ukończone misje |
| `PwaBadge` | `pwa_badge` | Zdobyte odznaki |
| `PwaSubscription` | `pwa_subscription` | Subskrypcje (activation_code, status, package_id, buyer_email, p24_order_id) |
| `PwaPackage` | `pwa_package` | Pakiety do kupienia (id, name, price, currency, active) |

## Integracje

### Przelewy24 (P24Service)
- `registerTransaction()` → redirect do bramki P24
- `verifyTransaction()` → webhook `/api/p24/notify`
- Signature: SHA-384 JSON z CRC key
- Flow: `/kup/{packageId}` → P24 → `/kup/powrot/{id}` + webhook → `status=active` → email z kodem
- Sandbox: `P24_SANDBOX=true`

### Cloudflare Turnstile
- `verify()` → POST do Cloudflare API
- W `dev`: zawsze `true`
- Używany: kontakt, waitlist, zakup

### Mailer (Resend)
- Prod: `resend+api://API_KEY@default`
- Dev: `null://null`
- Szablony: `purchase_confirmation`, `contact_confirmation`, `admin_notification`, `waitlist_confirmation`

### PWA Sync
- `POST /api/pwa/sync` — upsert children + missions + badges, zwraca `subscription` status
- `GET /api/pwa/sync/{deviceId}` — restore danych
- `POST /api/pwa/subscription/activate` — aktywacja kodu, binding do device_id
- CORS: tropo.app + localhost:5173 + tropo.local

## 4 postacie (CharacterService)

| ID | Nazwa | Gatunek | Kolor | Emoji |
|---|---|---|---|---|
| `scout` | Scout | Pies Tropiciel | `#4A90D9` | 🐕 |
| `blitz` | Blitz | Królik Sprinter | `#FF6B35` | 🐇 |
| `nova` | Nova | Szop Wynalazca | `#7B2D8E` | 🦝 |
| `echo` | Echo | Żółw Mędrzec | `#2D8E5E` | 🐢 |

## Bajki (StoryService)

Sezon 1: "Przebudzenie Tropo" — 7 odcinków. Dane hardcoded w PHP.
Szablony odcinków: `templates/story/episodes/*.html.twig`

## Bezpieczeństwo

- Auth: form_login `/login`, User entity
- Rate limiting: 5 prób / 15 min
- ROLE_ADMIN dla `/admin/*`
- `/api/pwa/*`: PUBLIC_ACCESS (device_id jako identyfikator)
- Turnstile na formularzach publicznych
- CSRF na wszystkich POST formularzach

## Tailwind CSS

Customowe kolory w `assets/styles/app.css` (`@theme`):
- `--color-scout: #3578E5` (niebieski)
- `--color-blitz: #E8612A` (pomarańczowy)
- `--color-nova: #7C3AED` (fioletowy)
- `--color-echo: #16A34A` (zielony)
- `--color-cream: #FFFDF7` (tło)
- `--color-dark: #1A1D1F` (tekst)

**UWAGA:** Arbitrary values `bg-[#hex]` NIE działają z symfonycasts/tailwind-bundle. Używaj customowych kolorów (`bg-nova`, `text-scout`) lub inline style.

Standardowe klasy buttonów: `.btn`, `.btn-primary`, `.btn-secondary`, `.btn-outline`, `.btn-lg`, `.btn-md` — zdefiniowane w `app.css` jako plain CSS (nie `@apply`).

## Tłumaczenia

- `messages.pl.yaml` — primary, kompletny
- `messages.en.yaml`, `messages.de.yaml`, `messages.uk.yaml` — częściowe (base, nav, footer)
- Locales włączane w SiteSettings (admin panel)

## Admin Panel (`/admin`)

Dashboard, Misje (filtry + inline edit + import/export JSON), Blog (CRUD), Komentarze, Kontakty, Opinie, Użytkownicy, Waitlist, Tracking, Ustawienia (maintenance, monetization, locales).

## Monetyzacja

- `SiteSettings.monetizationEnabled` steruje widocznością cennika, przycisków pobierania, strony zakupu
- Gdy wyłączona: `/cennik`, `/kup`, `/gra-planszowa` → redirect na stronę główną
- `PwaPackage` przechowuje cenę per pakiet (np. "base" = 70 PLN)

## Komendy

```bash
php bin/console app:create-admin email@example.com password
php bin/console app:import-missions missions.json [--fresh]
php bin/console app:export-missions
php bin/console app:seed-blog
php bin/console app:generate-social-content [--week=YYYY-WW] [--text-only] [--force]
php bin/console tailwind:build     # Rebuild Tailwind CSS
```

## Cache i proxy

- HTTP cache headers na publicznych kontrolerach (`setPublic()`, `setMaxAge()`)
- Cloudflare proxy: `TRUSTED_PROXIES` env var, `trusted_headers` w `framework.yaml`
- LiipImagine: 12 filter sets — `char_icon(_webp)`, `char_full(_webp)`, `logo(_webp)`, `hero_phone(_webp)`, `story_illustration(_webp)`, `og_image(_webp)`

## Deployment

- Capistrano 3.19 z custom Symfony tasks
- Branch: `main`
- NIGDY nie deployuj automatycznie — deployment ręczny
