Osadz logo w mailach, popraw naglowek i nadaj adresy krokom logowania
Deploy Polska Lokalnie / deploy (push) Successful in 12s

Maile:
- Logo jest zalacznikiem inline (cid:) zamiast obrazka z adresu serwisu.
  Wczesniej w mailach z lokalnego srodowiska (i w programach blokujacych
  obrazki z zewnatrz) w miejscu logo byla ikona bledu. Usuniety tekst
  zastepczy, ktory dublowal napis "Polska Lokalnie".

Naglowek:
- Menu nie nachodzi na logo ani panel przy zadnej szerokosci 1110-1920 px.
  Ponizej 1560 px "Ulubione" to samo serce (nazwa w dymku), mniejsze odstepy
  i przycisk "Dodaj ogloszenie" bez sztywnej szerokosci; w 1101-1270 px
  mniejszy napis logo i tekst przycisku.

Logowanie i rejestracja (standard routingu):
- Kazdy krok ma wlasny adres: /logowanie, /logowanie/nie-pamietam-hasla,
  /rejestracja, /rejestracja/osoba-prywatna, /rejestracja/firma,
  /rejestracja/sprawdz-poczte. Dzialaja F5, Wstecz/Dalej i nowa karta,
  formularz czysci sie takze przy Wstecz/Dalej, a cel powrotu po
  zalogowaniu nie ginie przy przejsciach miedzy krokami.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-22 11:26:47 +02:00
parent 72af24469a
commit e7402dbfed
8 changed files with 238 additions and 17 deletions
+51 -10
View File
@@ -3648,13 +3648,16 @@ function App() {
}, [location.pathname, navigationType]);
return (
<div className={`site-shell ${location.pathname === ROUTES.login ? 'login-shell' : ''}`}>
<div className={`site-shell ${LOGIN_PAGE_PATHS.has(location.pathname) ? 'login-shell' : ''}`}>
<Header onLogout={handleLogout} hasUnreadFavorites={unreadFavoriteIds.size > 0} onOpenNotification={handleOpenNotification} />
<main>
<Routes>
{/* Trasy publiczne */}
<Route path={ROUTES.home} element={<HomePage filters={filters} onOpenMap={openMap} />} />
<Route path={ROUTES.login} element={<LoginPage onLogin={handleAuthenticated} />} />
{/* Logowanie i rejestracja: kazdy krok pod wlasnym adresem, jeden komponent. */}
{Object.values(LOGIN_STEP_PATHS).map((path) => (
<Route key={path} path={path} element={<LoginPage onLogin={handleAuthenticated} />} />
))}
<Route path={ROUTES.terms} element={<TermsPage />} />
<Route path={ROUTES.privacy} element={<PrivacyPolicyPage />} />
<Route path={ROUTES.activation} element={<ActivationPage />} />
@@ -12086,12 +12089,32 @@ function AccountPublicProfilePage() {
type LoginStep = 'login' | 'register-choice' | 'register-private' | 'register-company' | 'register-pending' | 'forgot';
// Krok logowania/rejestracji wynika z adresu - kazdy ma wlasna trase.
const LOGIN_STEP_PATHS: Record<LoginStep, string> = {
login: ROUTES.login,
forgot: ROUTES.loginForgot,
'register-choice': ROUTES.register,
'register-private': ROUTES.registerPrivate,
'register-company': ROUTES.registerCompany,
'register-pending': ROUTES.registerPending,
};
function loginStepFromPath(pathname: string): LoginStep {
const entry = Object.entries(LOGIN_STEP_PATHS).find(([, path]) => path === pathname);
return (entry?.[0] as LoginStep | undefined) ?? 'login';
}
// Strony logowania i rejestracji maja wspolny uklad (login-shell).
const LOGIN_PAGE_PATHS = new Set(Object.values(LOGIN_STEP_PATHS));
// Dane firmy z rejestru REGON (GUS) - odpowiedz /api/gus/company.
type GusCompany = { name: string | null; nip: string; regon: string | null; address: string | null };
function LoginPage({ onLogin }: { onLogin: (user: AuthUser) => void }) {
const { login, register, forgotPassword, resendActivation } = useAuth();
const [step, setStep] = useState<LoginStep>('login');
const location = useLocation();
const navigate = useNavigate();
const step = loginStepFromPath(location.pathname);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
@@ -12153,7 +12176,7 @@ function LoginPage({ onLogin }: { onLogin: (user: AuthUser) => void }) {
try {
const pending = await action();
setPendingEmail(pending.email);
setStep('register-pending');
goToStep('register-pending');
// Backend potwierdza, czy SMS naprawde wyszedl - przy awarii bramki nie otwieramy okna na kod.
setOtpCodeSent(pending.phoneVerificationRequired);
if (pending.phoneVerificationRequired) {
@@ -12167,13 +12190,24 @@ function LoginPage({ onLogin }: { onLogin: (user: AuthUser) => void }) {
}
};
// Przejscie do innego kroku to zmiana adresu. Cel powrotu po zalogowaniu (state.from,
// ustawiany przez ProtectedRoute) idzie dalej, zeby nie zgubic go np. po wizycie w rejestracji.
const goToStep = (nextStep: LoginStep) => {
navigate(LOGIN_STEP_PATHS[nextStep], { state: location.state });
};
// Logowanie i rejestracja korzystaja z tych samych pol (e-mail, haslo). Formularz rejestracji
// - prywatny i firmowy - zawsze zaczyna sie pusty, zeby dane wpisane albo podstawione przez
// przegladarke przy logowaniu nie trafialy do nowego konta. E-mail zostaje tylko miedzy
// logowaniem a resetem hasla, gdzie chodzi o to samo konto.
const goToStep = (nextStep: LoginStep) => {
const keepsEmail = (step === 'login' || step === 'forgot') && (nextStep === 'login' || nextStep === 'forgot');
setStep(nextStep);
// logowaniem a resetem hasla, gdzie chodzi o to samo konto. Dziala tez dla Wstecz/Dalej.
const previousStepRef = useRef(step);
useEffect(() => {
const previousStep = previousStepRef.current;
previousStepRef.current = step;
if (previousStep === step) {
return;
}
const keepsEmail = (previousStep === 'login' || previousStep === 'forgot') && (step === 'login' || step === 'forgot');
setError(null);
setInfo(null);
setNotActivated(false);
@@ -12192,7 +12226,7 @@ function LoginPage({ onLogin }: { onLogin: (user: AuthUser) => void }) {
setCompanyAddress('');
setCompanyPhone('');
setAgreeTerms(false);
};
}, [step]);
const handleLoginSubmit = (event: ReactFormEvent<HTMLFormElement>) => {
event.preventDefault();
@@ -12749,7 +12783,11 @@ function LoginPage({ onLogin }: { onLogin: (user: AuthUser) => void }) {
</form>
)}
{step === 'register-pending' && (
{/* Ekran po rejestracji ma sens tylko zaraz po niej - e-mail nie trafia do adresu,
wiec po odswiezeniu strony wracamy do rejestracji. */}
{step === 'register-pending' && !pendingEmail && <Navigate to={ROUTES.register} replace />}
{step === 'register-pending' && pendingEmail && (
<div className="login-card register-pending-card">
<span className="register-pending-icon"><Icon name="mail" /></span>
<h2>Sprawdź swoją skrzynkę e-mail</h2>
@@ -23542,6 +23580,9 @@ function Header({
<NavLink
to={ROUTES.favorites}
className={({ isActive }) => (isActive ? 'icon-text favorites-shortcut active' : 'icon-text favorites-shortcut')}
// Na wezszych ekranach podpis jest ukryty - nazwa zostaje w dymku i dla czytnikow ekranu.
aria-label="Ulubione"
title="Ulubione"
>
<Icon name="heart" />
<span>Ulubione</span>
+6
View File
@@ -28,6 +28,12 @@ export const ROUTES = {
creditCalculator: '/kalkulator-zdolnosci',
map: '/mapa',
login: '/logowanie',
// Kroki logowania i rejestracji - kazdy ma wlasny adres (F5, Wstecz/Dalej, nowa karta).
loginForgot: '/logowanie/nie-pamietam-hasla',
register: '/rejestracja',
registerPrivate: '/rejestracja/osoba-prywatna',
registerCompany: '/rejestracja/firma',
registerPending: '/rejestracja/sprawdz-poczte',
account: '/konto',
accountSearches: '/konto/wyszukiwania',
accountPriceAlerts: '/konto/alerty-cenowe',
+56 -1
View File
@@ -288,6 +288,28 @@ svg {
position: relative;
}
/* Ponizej 1560 px menu glowne nie miescilo sie miedzy logo a panelem i nachodzilo na oba -
"Ulubione" zostaje samym sercem (jak dzwonek i wiadomosci), podpis jest w dymku. */
@media (max-width: 1560px) {
.favorites-shortcut span {
display: none;
}
.header {
gap: 20px;
}
.nav {
gap: clamp(14px, 1.2vw, 44px);
}
.post-button {
min-width: 0;
padding-left: 14px;
padding-right: 14px;
}
}
/* Skrot do wiadomosci w pasku gornym - plakietka z liczba nieprzeczytanych. */
.messages-shortcut {
position: relative;
@@ -12134,7 +12156,13 @@ a.city-card:hover {
}
.post-button {
min-width: 210px;
min-width: 0;
padding-left: 14px;
padding-right: 14px;
}
.header-actions {
gap: 16px;
}
.hero-inner,
@@ -12325,6 +12353,33 @@ a.city-card:hover {
}
}
/* Tuz nad ukladem dwurzedowym (<= 1100 px) menu w jednej linii potrzebuje ciasniejszych odstepow. */
@media (min-width: 1101px) and (max-width: 1270px) {
.header {
gap: 10px;
}
.nav {
gap: 10px;
}
.header-actions {
gap: 10px;
}
.brand strong {
font-size: 21px;
}
.brand-logo-img {
height: 40px;
}
.post-button span {
font-size: 13px;
}
}
@media (max-width: 1100px) {
.header {
grid-template-columns: 1fr auto;
+21
View File
@@ -22,6 +22,10 @@ const PUBLIC_ROUTES = [
ROUTES.guides,
ROUTES.guideBuying,
ROUTES.login,
ROUTES.loginForgot,
ROUTES.register,
ROUTES.registerPrivate,
ROUTES.registerCompany,
];
const PROTECTED_ROUTES = [
@@ -397,3 +401,20 @@ test.describe('Wiadomosci', () => {
await page.waitForFunction(() => window.location.pathname === '/wiadomosci');
});
});
test.describe('Logowanie i rejestracja', () => {
test('kroki maja wlasne adresy, a Wstecz wraca do poprzedniego kroku', async ({ page }) => {
await goto(page, ROUTES.login);
await page.getByRole('button', { name: 'Zarejestruj się' }).first().click();
await page.waitForFunction(() => window.location.pathname === '/rejestracja');
await page.locator('.register-choice-option').first().click();
await page.waitForFunction(() => window.location.pathname === '/rejestracja/osoba-prywatna');
await page.goBack();
await page.waitForFunction(() => window.location.pathname === '/rejestracja');
});
test('ekran "sprawdz poczte" bez rejestracji wraca do rejestracji', async ({ page }) => {
await goto(page, ROUTES.registerPending);
await page.waitForFunction(() => window.location.pathname === '/rejestracja');
});
});