Wybór klubu przy logowaniu
Klub, w którym klient gra, decyduje o wszystkim, co widzi po zalogowaniu: grafiku, ofercie, cenach, kalendarzu rezerwacji. Ten dokument opisuje, skąd system bierze tę informację i co się dzieje, gdy jej nie ma.
👤 Instrukcja dla klienta
Pytanie o klub
Na ekranie logowania, zanim klient poda dane, pojawia się karta „Wybierz klub" z listą lokalizacji. Pytanie pada tylko wtedy, gdy klub ma więcej niż jedno miasto — przy jednej lokalizacji wybór nie miałby treści.
Po wybraniu klubu pytanie już nie wraca: nad formularzem logowania widać plakietkę z nazwą klubu i odnośnikiem „Zmień", którym można wrócić do listy.
Co robi ten wybór
Wybrany klub zostaje zapisany na koncie klienta jako jego miasto. Od tej chwili grafik, oferta i rezerwacje pokazują tę lokalizację — także przy kolejnych logowaniach i na innym urządzeniu, bo miasto siedzi na koncie, a nie w przeglądarce.
Klient może grać w innym mieście: na ekranie rezerwacji kortu da się przełączyć miasto na jedną rezerwację. Taki wybór jest jednorazowy — nie zmienia klubu przypisanego do konta.
Zmiana klubu na stałe
Dwie drogi:
- wylogować się i wybrać inny klub przy następnym logowaniu,
- zmienić Preferowane miasto w profilu.
Recepcja może zrobić to samo z poziomu kartoteki klienta.
🔧 Dokumentacja techniczna
Skąd bierze się miasto
Kolejność jest zawsze ta sama i nie ma od niej wyjątków:
?city=w adresie — o ile ekran w ogóle na to pozwala,user_metadata.preferred_cityz konta (kolumnauser.preferredCity),DEFAULT_CITYzsettings.tsjako ostatnia deska ratunku.
Punkt 3 jest awaryjny, nie domyślny. Konto bez miasta to konto, którego nikt jeszcze o klub nie zapytał — i tak długo, jak ten stan trwa, nie wolno go nigdzie utrwalić.
Ciasteczka
| Ciasteczko | Kto zapisuje | Znaczenie |
|---|---|---|
selected-city | picker klubu oraz nagłówek panelu | miasto, którym filtrujemy po stronie serwera |
selected-street | jw. | lokalizacja w mieście albo ALL |
club-picked | wyłącznie picker klubu | ktoś naprawdę wybrał, a nie: coś podstawiono |
club-picked istnieje, bo selected-city nie odpowiada na pytanie „czy ktoś
to wybrał?". Nagłówek panelu zapisuje je przy każdym renderze, a dla konta bez
miasta zapisywaną wartością jest DEFAULT_CITY. Bez rozróżnienia zgadywanie
awaryjne stawało się faktem:
konto bez miasta → panel pokazuje Opole → nagłówek zapisuje cookie "Opole"
→ logowanie: cookie jest, więc picker się nie pokazuje
→ adoptChosenClub przepisuje "Opole" na konto na stałe
Dlatego obowiązują dwie zasady:
getChosenClubCity()czyta miasto tylko przyclub-picked=1,- nagłówek (
GlobalCitySelectClient) nie zapisuje ciasteczka lokalizacji, gdy klient nie ma własnegopreferred_city; dla pracownika zapisuje zawsze, bo tam miasto jest narzędziem pracy, a nie przynależnością.
Przeniesienie wyboru na konto
Klub wybiera się przed zalogowaniem, więc nie ma jeszcze konta, do którego
można go dopiąć. Przepisuje go adoptChosenClub() zaraz po zalogowaniu, w
/api/auth/refresh-session. Każde logowanie przechodzi przez ten adres:
formularz / provider / kod SMS
→ /api/auth/refresh-session?to=<returnTo>
→ adoptChosenClub() zapis preferred_city na koncie
→ clearSessionDataCookies() unieważnienie cache'u sesji
→ <returnTo>
Dwa powody, dla których to musi być właśnie ten hop:
returnToomija stronę główną. Middleware wkłada żądaną ścieżkę doreturnTo, a formularz robi tam twardą nawigację. Gdy adopcja siedziała wapp/page.tsx, logowanie z deep linku (mail, powiadomienie, zapamiętana strona w PWA) w ogóle nie dotykało/— klient wybierał Lublin i lądował na mieście, które miał na koncie wcześniej.- Cache sesji. Better Auth odpowiada na
getSessionz podpisanego ciasteczka przez cały czas życiacookieCache, więc zapis do wiersza użytkownika jest niewidoczny aż do jego wygaśnięcia. Komponent serwerowy nie skasuje ciasteczka; route handler tak.
Aplikacja natywna ma własny słoik na ciasteczka, więc dla niej ostatnim
momentem, w którym wybór jest jeszcze osiągalny, jest
/api/auth/mobile-handoff — i tam też stoi adoptChosenClub().
app/page.tsx nadal wywołuje adopcję. Jest idempotentna (zwraca false, gdy
miasto się zgadza) i zostaje jako siatka bezpieczeństwa dla ścieżek lądujących
prosto na /.
Skutek uboczny wdrożenia
club-picked jest nowe, więc żadna istniejąca sesja go nie ma. Przy
pierwszym logowaniu po wdrożeniu każdy klient zobaczy pytanie o klub jeden raz,
niezależnie od tego, co ma na koncie. To zamierzone: dokładnie tak odblokowują
się konta, na których pytanie zostało zdławione fałszywym ciasteczkiem.
Pliki
| Plik | Rola |
|---|---|
app/auth/login/club-picker.tsx | karta wyboru klubu |
app/auth/login/login-entry.tsx | decyzja, czy pytać; zapis wyboru |
app/auth/login/page.tsx | odczyt club-picked przed renderem |
lib/club-selection.ts | getChosenClubCity, adoptChosenClub |
lib/utils/location.ts | setChosenClub, setSelectedLocation |
app/api/auth/refresh-session/route.ts | adopcja + unieważnienie cache'u sesji |
app/api/auth/mobile-handoff/route.ts | adopcja przed przekazaniem sesji do aplikacji |
components/layout/global-city-select-client.tsx | nagłówek panelu; zapis ciasteczka lokalizacji |
Testy
__tests__/lib/club-selection.test.ts— ciasteczko bezclub-pickednie jest wyborem i nie nadpisuje miasta na koncie.__tests__/api/refresh-session-route.test.ts— adopcja przy logowaniu z deep linkiem, unieważnienie cache'u, odrzucenie obcegoto.__tests__/components/global-city-select-client.test.tsx— nagłówek nie zapisuje ciasteczka klientowi bez własnego miasta, pracownikowi zapisuje.