Skip to main content

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:

  1. ?city= w adresie — o ile ekran w ogóle na to pozwala,
  2. user_metadata.preferred_city z konta (kolumna user.preferredCity),
  3. DEFAULT_CITY z settings.ts jako 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

CiasteczkoKto zapisujeZnaczenie
selected-citypicker klubu oraz nagłówek panelumiasto, którym filtrujemy po stronie serwera
selected-streetjw.lokalizacja w mieście albo ALL
club-pickedwyłącznie picker klubuktoś 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 przy club-picked=1,
  • nagłówek (GlobalCitySelectClient) nie zapisuje ciasteczka lokalizacji, gdy klient nie ma własnego preferred_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:

  • returnTo omija stronę główną. Middleware wkłada żądaną ścieżkę do returnTo, a formularz robi tam twardą nawigację. Gdy adopcja siedziała w app/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 getSession z podpisanego ciasteczka przez cały czas życia cookieCache, 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

PlikRola
app/auth/login/club-picker.tsxkarta wyboru klubu
app/auth/login/login-entry.tsxdecyzja, czy pytać; zapis wyboru
app/auth/login/page.tsxodczyt club-picked przed renderem
lib/club-selection.tsgetChosenClubCity, adoptChosenClub
lib/utils/location.tssetChosenClub, setSelectedLocation
app/api/auth/refresh-session/route.tsadopcja + unieważnienie cache'u sesji
app/api/auth/mobile-handoff/route.tsadopcja przed przekazaniem sesji do aplikacji
components/layout/global-city-select-client.tsxnagłówek panelu; zapis ciasteczka lokalizacji

Testy

  • __tests__/lib/club-selection.test.ts — ciasteczko bez club-picked nie 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 obcego to.
  • __tests__/components/global-city-select-client.test.tsx — nagłówek nie zapisuje ciasteczka klientowi bez własnego miasta, pracownikowi zapisuje.