Popraw rejestracje, kod SMS, opisy wysylki i widok wiadomosci
Deploy Polska Lokalnie / deploy (push) Successful in 11s
Deploy Polska Lokalnie / deploy (push) Successful in 11s
Rejestracja (/logowanie):
- Formularz rejestracji (osoba prywatna i firma) zawsze zaczyna sie pusty.
Logowanie i rejestracja dzielily pola e-mail i hasla, wiec dane wpisane
albo podstawione przez przegladarke przy logowaniu trafialy do nowego konta.
- "Powtorz haslo" ma wlasny przelacznik widocznosci; wczesniej oko przy
"Haslo" odslanialo oba pola, a ikona przy "Powtorz haslo" nie reagowala.
- Przycisk oka ma normalny obszar klikniecia (mial zerowy rozmiar).
Kod SMS:
- Okno potwierdzania numeru odlicza czas od SMS-a z rejestracji, a odmowa
serwera ("Kolejny mozesz zamowic za N s") odlicza czas w dol zamiast
pokazywac stala liczbe sekund. To samo w ustawieniach konta.
Panel admina:
- "Wysylka kampanii e-mail aktywna" -> "Wysylka e-maili aktywna" z opisem:
przelacznik blokuje wszystkie e-maile (aktywacja, reset hasla,
powiadomienia), nie tylko kampanie. Podobny opis przy SMS.
Wiadomosci:
- Puste pole pisania bez "Napisz wiadomosc do ...".
- Naglowek rozmowy "Nazwa - opis" zamiast sklejonego tekstu.
- Na liscie rozmow jedna miniatura ogloszenia zamiast dwoch.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+66
-10
@@ -10481,6 +10481,8 @@ function AccountSettingsPage({ onSearchPreferencesSaved }: { onSearchPreferences
|
|||||||
const [otpInfo, setOtpInfo] = useState('');
|
const [otpInfo, setOtpInfo] = useState('');
|
||||||
const [otpBusy, setOtpBusy] = useState(false);
|
const [otpBusy, setOtpBusy] = useState(false);
|
||||||
const [otpCooldown, setOtpCooldown] = useState(0);
|
const [otpCooldown, setOtpCooldown] = useState(0);
|
||||||
|
// Serwer odmowil kolejnego kodu - komunikat z odliczanym czasem zamiast stalej liczby sekund.
|
||||||
|
const [otpCooldownRejected, setOtpCooldownRejected] = useState(false);
|
||||||
|
|
||||||
// --- Preferencje wyszukiwania ---
|
// --- Preferencje wyszukiwania ---
|
||||||
const [searchPrefs, setSearchPrefs] = useState({
|
const [searchPrefs, setSearchPrefs] = useState({
|
||||||
@@ -10864,6 +10866,7 @@ function AccountSettingsPage({ onSearchPreferencesSaved }: { onSearchPreferences
|
|||||||
setOtpBusy(true);
|
setOtpBusy(true);
|
||||||
setOtpError('');
|
setOtpError('');
|
||||||
setOtpInfo('');
|
setOtpInfo('');
|
||||||
|
setOtpCooldownRejected(false);
|
||||||
try {
|
try {
|
||||||
await resendPhoneOtp(user.email);
|
await resendPhoneOtp(user.email);
|
||||||
setOtpInfo(`Wysłaliśmy kod SMS na numer ${user.phone ?? ''}.`);
|
setOtpInfo(`Wysłaliśmy kod SMS na numer ${user.phone ?? ''}.`);
|
||||||
@@ -10871,7 +10874,14 @@ function AccountSettingsPage({ onSearchPreferencesSaved }: { onSearchPreferences
|
|||||||
setIsOtpOpen(true);
|
setIsOtpOpen(true);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : 'Nie udało się wysłać kodu SMS.';
|
const message = error instanceof Error ? error.message : 'Nie udało się wysłać kodu SMS.';
|
||||||
if (isOtpOpen) {
|
// Za wczesnie na kolejny kod - poprzedni nadal jest wazny, wiec okno otwieramy
|
||||||
|
// i odliczamy czas podany przez serwer ("Kolejny możesz zamówić za N s").
|
||||||
|
const secondsLeft = Number(/za (\d+) s/.exec(message)?.[1]);
|
||||||
|
if ((error as { status?: number }).status === 429 && secondsLeft > 0) {
|
||||||
|
setOtpCooldown(secondsLeft);
|
||||||
|
setOtpCooldownRejected(true);
|
||||||
|
setIsOtpOpen(true);
|
||||||
|
} else if (isOtpOpen) {
|
||||||
// Okno juz otwarte (ponowna wysylka) - blad pokazujemy w nim.
|
// Okno juz otwarte (ponowna wysylka) - blad pokazujemy w nim.
|
||||||
setOtpError(message);
|
setOtpError(message);
|
||||||
} else {
|
} else {
|
||||||
@@ -11873,6 +11883,9 @@ function AccountSettingsPage({ onSearchPreferencesSaved }: { onSearchPreferences
|
|||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
{otpError && <p className="auth-status-error" role="alert">{otpError}</p>}
|
{otpError && <p className="auth-status-error" role="alert">{otpError}</p>}
|
||||||
|
{otpCooldownRejected && otpCooldown > 0 && (
|
||||||
|
<p className="auth-status-error" role="alert">Kod został już wysłany. Kolejny możesz zamówić za {otpCooldown} s</p>
|
||||||
|
)}
|
||||||
{otpInfo && !otpError && <p className="otp-modal-ok">{otpInfo}</p>}
|
{otpInfo && !otpError && <p className="otp-modal-ok">{otpInfo}</p>}
|
||||||
<button className="auth-status-btn" type="submit" disabled={otpBusy || otpCode.length < 6}>
|
<button className="auth-status-btn" type="submit" disabled={otpBusy || otpCode.length < 6}>
|
||||||
{otpBusy ? 'Sprawdzanie...' : 'Potwierdź numer'}
|
{otpBusy ? 'Sprawdzanie...' : 'Potwierdź numer'}
|
||||||
@@ -12092,6 +12105,8 @@ function LoginPage({ onLogin }: { onLogin: (user: AuthUser) => void }) {
|
|||||||
const [companyPhone, setCompanyPhone] = useState('');
|
const [companyPhone, setCompanyPhone] = useState('');
|
||||||
const [agreeTerms, setAgreeTerms] = useState(false);
|
const [agreeTerms, setAgreeTerms] = useState(false);
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
// "Powtórz hasło" ma własny przełącznik widoczności - niezależny od pola "Hasło".
|
||||||
|
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [info, setInfo] = useState<string | null>(null);
|
const [info, setInfo] = useState<string | null>(null);
|
||||||
const [submitting, setSubmitting] = useState(false);
|
const [submitting, setSubmitting] = useState(false);
|
||||||
@@ -12101,6 +12116,8 @@ function LoginPage({ onLogin }: { onLogin: (user: AuthUser) => void }) {
|
|||||||
const [otpModalOpen, setOtpModalOpen] = useState(false);
|
const [otpModalOpen, setOtpModalOpen] = useState(false);
|
||||||
// Czy kod SMS faktycznie wyszedl z bramki - decyduje o tresci ekranu po rejestracji.
|
// Czy kod SMS faktycznie wyszedl z bramki - decyduje o tresci ekranu po rejestracji.
|
||||||
const [otpCodeSent, setOtpCodeSent] = useState(false);
|
const [otpCodeSent, setOtpCodeSent] = useState(false);
|
||||||
|
// Kiedy wyszedl ostatni kod SMS - okno odlicza od tej chwili czas do ponownej wysylki.
|
||||||
|
const [otpSentAt, setOtpSentAt] = useState<number | null>(null);
|
||||||
const [notActivated, setNotActivated] = useState(false);
|
const [notActivated, setNotActivated] = useState(false);
|
||||||
|
|
||||||
const runAuth = async (action: () => Promise<AuthUser>) => {
|
const runAuth = async (action: () => Promise<AuthUser>) => {
|
||||||
@@ -12140,6 +12157,7 @@ function LoginPage({ onLogin }: { onLogin: (user: AuthUser) => void }) {
|
|||||||
// Backend potwierdza, czy SMS naprawde wyszedl - przy awarii bramki nie otwieramy okna na kod.
|
// Backend potwierdza, czy SMS naprawde wyszedl - przy awarii bramki nie otwieramy okna na kod.
|
||||||
setOtpCodeSent(pending.phoneVerificationRequired);
|
setOtpCodeSent(pending.phoneVerificationRequired);
|
||||||
if (pending.phoneVerificationRequired) {
|
if (pending.phoneVerificationRequired) {
|
||||||
|
setOtpSentAt(Date.now());
|
||||||
setOtpModalOpen(true);
|
setOtpModalOpen(true);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -12149,11 +12167,31 @@ function LoginPage({ onLogin }: { onLogin: (user: AuthUser) => void }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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 goToStep = (nextStep: LoginStep) => {
|
||||||
|
const keepsEmail = (step === 'login' || step === 'forgot') && (nextStep === 'login' || nextStep === 'forgot');
|
||||||
setStep(nextStep);
|
setStep(nextStep);
|
||||||
setError(null);
|
setError(null);
|
||||||
setInfo(null);
|
setInfo(null);
|
||||||
setNotActivated(false);
|
setNotActivated(false);
|
||||||
|
if (!keepsEmail) {
|
||||||
|
setEmail('');
|
||||||
|
}
|
||||||
|
setPassword('');
|
||||||
|
setConfirmPassword('');
|
||||||
|
setShowPassword(false);
|
||||||
|
setShowConfirmPassword(false);
|
||||||
|
setFirstName('');
|
||||||
|
setLastName('');
|
||||||
|
setPhone('');
|
||||||
|
setCompanyName('');
|
||||||
|
setNip('');
|
||||||
|
setCompanyAddress('');
|
||||||
|
setCompanyPhone('');
|
||||||
|
setAgreeTerms(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLoginSubmit = (event: ReactFormEvent<HTMLFormElement>) => {
|
const handleLoginSubmit = (event: ReactFormEvent<HTMLFormElement>) => {
|
||||||
@@ -12498,7 +12536,7 @@ function LoginPage({ onLogin }: { onLogin: (user: AuthUser) => void }) {
|
|||||||
<span>Powtórz hasło</span>
|
<span>Powtórz hasło</span>
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input
|
||||||
type={showPassword ? 'text' : 'password'}
|
type={showConfirmPassword ? 'text' : 'password'}
|
||||||
placeholder="Powtórz swoje hasło"
|
placeholder="Powtórz swoje hasło"
|
||||||
value={confirmPassword}
|
value={confirmPassword}
|
||||||
onChange={(event) => setConfirmPassword(event.target.value)}
|
onChange={(event) => setConfirmPassword(event.target.value)}
|
||||||
@@ -12506,7 +12544,14 @@ function LoginPage({ onLogin }: { onLogin: (user: AuthUser) => void }) {
|
|||||||
minLength={6}
|
minLength={6}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<Icon name="eye" />
|
<button
|
||||||
|
type="button"
|
||||||
|
className="login-eye"
|
||||||
|
aria-label={showConfirmPassword ? 'Ukryj hasło' : 'Pokaż hasło'}
|
||||||
|
onClick={() => setShowConfirmPassword((current) => !current)}
|
||||||
|
>
|
||||||
|
<Icon name="eye" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -12668,7 +12713,7 @@ function LoginPage({ onLogin }: { onLogin: (user: AuthUser) => void }) {
|
|||||||
<span>Powtórz hasło</span>
|
<span>Powtórz hasło</span>
|
||||||
<div>
|
<div>
|
||||||
<input
|
<input
|
||||||
type={showPassword ? 'text' : 'password'}
|
type={showConfirmPassword ? 'text' : 'password'}
|
||||||
placeholder="Powtórz swoje hasło"
|
placeholder="Powtórz swoje hasło"
|
||||||
value={confirmPassword}
|
value={confirmPassword}
|
||||||
onChange={(event) => setConfirmPassword(event.target.value)}
|
onChange={(event) => setConfirmPassword(event.target.value)}
|
||||||
@@ -12676,7 +12721,14 @@ function LoginPage({ onLogin }: { onLogin: (user: AuthUser) => void }) {
|
|||||||
minLength={6}
|
minLength={6}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<Icon name="eye" />
|
<button
|
||||||
|
type="button"
|
||||||
|
className="login-eye"
|
||||||
|
aria-label={showConfirmPassword ? 'Ukryj hasło' : 'Pokaż hasło'}
|
||||||
|
onClick={() => setShowConfirmPassword((current) => !current)}
|
||||||
|
>
|
||||||
|
<Icon name="eye" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -12756,6 +12808,8 @@ function LoginPage({ onLogin }: { onLogin: (user: AuthUser) => void }) {
|
|||||||
<PhoneOtpModal
|
<PhoneOtpModal
|
||||||
email={pendingEmail}
|
email={pendingEmail}
|
||||||
phone={pendingPhone}
|
phone={pendingPhone}
|
||||||
|
lastSentAt={otpSentAt}
|
||||||
|
onCodeSent={() => setOtpSentAt(Date.now())}
|
||||||
onClose={() => setOtpModalOpen(false)}
|
onClose={() => setOtpModalOpen(false)}
|
||||||
onVerified={() => { setOtpModalOpen(false); setInfo('Numer telefonu został potwierdzony.'); }}
|
onVerified={() => { setOtpModalOpen(false); setInfo('Numer telefonu został potwierdzony.'); }}
|
||||||
/>
|
/>
|
||||||
@@ -25122,9 +25176,7 @@ function MessagesPage({
|
|||||||
{/* Czego dotyczy rozmowa - widac od razu, bez wchodzenia w watek. */}
|
{/* Czego dotyczy rozmowa - widac od razu, bez wchodzenia w watek. */}
|
||||||
{thread.listing && (
|
{thread.listing && (
|
||||||
<span className="messages-thread-offer" title={`${thread.listing.title} • ${formatPrice(thread.listing.price)}`}>
|
<span className="messages-thread-offer" title={`${thread.listing.title} • ${formatPrice(thread.listing.price)}`}>
|
||||||
{thread.listing.thumbnail && (
|
{/* Miniatura ogloszenia jest juz awatarem watku - tu sam tytul i cena. */}
|
||||||
<i style={{ backgroundImage: `url(${thread.listing.thumbnail})` }} aria-hidden="true" />
|
|
||||||
)}
|
|
||||||
<em>{thread.listing.title}</em>
|
<em>{thread.listing.title}</em>
|
||||||
<b>{formatPrice(thread.listing.price)}</b>
|
<b>{formatPrice(thread.listing.price)}</b>
|
||||||
</span>
|
</span>
|
||||||
@@ -25153,7 +25205,9 @@ function MessagesPage({
|
|||||||
) : (
|
) : (
|
||||||
<strong>{activeThread.name}</strong>
|
<strong>{activeThread.name}</strong>
|
||||||
)}
|
)}
|
||||||
<small>{activeThread.status}</small>
|
{activeThread.status && (
|
||||||
|
<small><span className="messages-chat-status-sep" aria-hidden="true"> - </span>{activeThread.status}</small>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="messages-chat-listing">
|
<div className="messages-chat-listing">
|
||||||
@@ -25222,9 +25276,11 @@ function MessagesPage({
|
|||||||
sendMessage();
|
sendMessage();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
// Puste pole do pisania; podpowiedz zostaje tylko przy blokadzie, zeby bylo jasne, czemu nie da sie pisac.
|
||||||
placeholder={isActiveThreadBlocked
|
placeholder={isActiveThreadBlocked
|
||||||
? (activeThread.blockedByMe ? 'Zablokowałeś tego użytkownika. Odblokuj go, aby pisać.' : 'Nie możesz pisać do tego użytkownika.')
|
? (activeThread.blockedByMe ? 'Zablokowałeś tego użytkownika. Odblokuj go, aby pisać.' : 'Nie możesz pisać do tego użytkownika.')
|
||||||
: `Napisz wiadomość do ${activeThread.name}...`}
|
: ''}
|
||||||
|
aria-label={`Wiadomość do ${activeThread.name}`}
|
||||||
maxLength={2000}
|
maxLength={2000}
|
||||||
/>
|
/>
|
||||||
<button type="button" className="send" aria-label="Wyślij wiadomość" onClick={sendMessage} disabled={isActiveThreadBlocked || isSending}><Icon name="arrow" /></button>
|
<button type="button" className="send" aria-label="Wyślij wiadomość" onClick={sendMessage} disabled={isActiveThreadBlocked || isSending}><Icon name="arrow" /></button>
|
||||||
|
|||||||
@@ -128,10 +128,16 @@ export function PasswordResetPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Odstep miedzy wysylkami kodu - ten sam, ktory wymusza backend (RESEND_COOLDOWN_SECONDS).
|
||||||
|
const OTP_RESEND_COOLDOWN_SECONDS = 60;
|
||||||
|
|
||||||
// Okno potwierdzenia numeru telefonu kodem SMS (OTP), pokazywane po rejestracji z podanym telefonem.
|
// Okno potwierdzenia numeru telefonu kodem SMS (OTP), pokazywane po rejestracji z podanym telefonem.
|
||||||
export function PhoneOtpModal({ email, phone, onClose, onVerified }: {
|
// lastSentAt - kiedy wyszedl ostatni kod; od niego odliczamy czas do mozliwosci ponownej wysylki.
|
||||||
|
export function PhoneOtpModal({ email, phone, lastSentAt, onCodeSent, onClose, onVerified }: {
|
||||||
email: string;
|
email: string;
|
||||||
phone: string;
|
phone: string;
|
||||||
|
lastSentAt?: number | null;
|
||||||
|
onCodeSent?: () => void;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onVerified: () => void;
|
onVerified: () => void;
|
||||||
}) {
|
}) {
|
||||||
@@ -140,7 +146,11 @@ export function PhoneOtpModal({ email, phone, onClose, onVerified }: {
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [info, setInfo] = useState<string | null>(null);
|
const [info, setInfo] = useState<string | null>(null);
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
const [cooldown, setCooldown] = useState(0);
|
const [cooldown, setCooldown] = useState(() => (
|
||||||
|
lastSentAt ? Math.max(0, OTP_RESEND_COOLDOWN_SECONDS - Math.floor((Date.now() - lastSentAt) / 1000)) : 0
|
||||||
|
));
|
||||||
|
// Odmowa serwera ("Kolejny możesz zamówić za N s") - pokazujemy ja z odliczanym czasem.
|
||||||
|
const [cooldownRejected, setCooldownRejected] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (cooldown <= 0) {
|
if (cooldown <= 0) {
|
||||||
@@ -167,13 +177,20 @@ export function PhoneOtpModal({ email, phone, onClose, onVerified }: {
|
|||||||
|
|
||||||
const resend = async () => {
|
const resend = async () => {
|
||||||
setError(null);
|
setError(null);
|
||||||
|
setCooldownRejected(false);
|
||||||
try {
|
try {
|
||||||
await resendPhoneOtp(email);
|
await resendPhoneOtp(email);
|
||||||
setInfo('Wysłaliśmy nowy kod SMS.');
|
setInfo('Wysłaliśmy nowy kod SMS.');
|
||||||
// Zgodne z odstepem wymuszanym przez backend (RESEND_COOLDOWN_SECONDS).
|
setCooldown(OTP_RESEND_COOLDOWN_SECONDS);
|
||||||
setCooldown(60);
|
onCodeSent?.();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(errMsg(e));
|
const secondsLeft = Number(/za (\d+) s/.exec(errMsg(e))?.[1]);
|
||||||
|
if ((e as { status?: number }).status === 429 && secondsLeft > 0) {
|
||||||
|
setCooldown(secondsLeft);
|
||||||
|
setCooldownRejected(true);
|
||||||
|
} else {
|
||||||
|
setError(errMsg(e));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -197,6 +214,9 @@ export function PhoneOtpModal({ email, phone, onClose, onVerified }: {
|
|||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
{error && <p className="auth-status-error" role="alert">{error}</p>}
|
{error && <p className="auth-status-error" role="alert">{error}</p>}
|
||||||
|
{cooldownRejected && cooldown > 0 && (
|
||||||
|
<p className="auth-status-error" role="alert">Kod został już wysłany. Kolejny możesz zamówić za {cooldown} s</p>
|
||||||
|
)}
|
||||||
{info && <p className="otp-modal-ok">{info}</p>}
|
{info && <p className="otp-modal-ok">{info}</p>}
|
||||||
<button className="auth-status-btn" type="submit" disabled={busy || code.length < 6}>{busy ? 'Sprawdzanie...' : 'Potwierdź numer'}</button>
|
<button className="auth-status-btn" type="submit" disabled={busy || code.length < 6}>{busy ? 'Sprawdzanie...' : 'Potwierdź numer'}</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -844,11 +844,15 @@ function SmtpCard({ Icon }: { Icon: IconComponent }) {
|
|||||||
<label>Login SMTP / adres nadawcy
|
<label>Login SMTP / adres nadawcy
|
||||||
<input value={config.username ?? ''} onChange={(e) => set({ username: e.target.value })} placeholder="np. marketing@twojadomena.pl" />
|
<input value={config.username ?? ''} onChange={(e) => set({ username: e.target.value })} placeholder="np. marketing@twojadomena.pl" />
|
||||||
</label>
|
</label>
|
||||||
<label>Wysyłka kampanii e-mail aktywna
|
<label>Wysyłka e-maili aktywna
|
||||||
<select value={config.enabled ? 'on' : 'off'} onChange={(e) => set({ enabled: e.target.value === 'on' })}>
|
<select value={config.enabled ? 'on' : 'off'} onChange={(e) => set({ enabled: e.target.value === 'on' })}>
|
||||||
<option value="off">Wyłączone (sandbox)</option>
|
<option value="off">Wyłączone (sandbox)</option>
|
||||||
<option value="on">Włączone</option>
|
<option value="on">Włączone</option>
|
||||||
</select>
|
</select>
|
||||||
|
<small className="mkt-field-note">
|
||||||
|
Dotyczy wszystkich e-maili serwisu: linków aktywacyjnych, resetu hasła, powiadomień o wiadomościach
|
||||||
|
i kampanii. Po wyłączeniu nic nie jest wysyłane.
|
||||||
|
</small>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{error && <p className="mkt-error">{error}</p>}
|
{error && <p className="mkt-error">{error}</p>}
|
||||||
@@ -955,6 +959,10 @@ function SmsCard({ Icon }: { Icon: IconComponent }) {
|
|||||||
<option value="off">Wyłączone (sandbox)</option>
|
<option value="off">Wyłączone (sandbox)</option>
|
||||||
<option value="on">Włączone</option>
|
<option value="on">Włączone</option>
|
||||||
</select>
|
</select>
|
||||||
|
<small className="mkt-field-note">
|
||||||
|
Dotyczy wszystkich SMS-ów serwisu: kodów weryfikujących telefon przy rejestracji i kampanii.
|
||||||
|
Po wyłączeniu nic nie jest wysyłane.
|
||||||
|
</small>
|
||||||
</label>
|
</label>
|
||||||
<label>Pełny URL endpointu bramki SMS
|
<label>Pełny URL endpointu bramki SMS
|
||||||
<input value={config.endpointUrl ?? ''} onChange={(e) => set({ endpointUrl: e.target.value })} placeholder="https://api.softspm.pl/send_sms_api.php" />
|
<input value={config.endpointUrl ?? ''} onChange={(e) => set({ endpointUrl: e.target.value })} placeholder="https://api.softspm.pl/send_sms_api.php" />
|
||||||
|
|||||||
+25
-10
@@ -16791,15 +16791,6 @@ a.city-card:hover {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.messages-thread-offer i {
|
|
||||||
background-position: center;
|
|
||||||
background-size: cover;
|
|
||||||
border-radius: 4px;
|
|
||||||
flex: none;
|
|
||||||
height: 16px;
|
|
||||||
width: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages-thread-offer em {
|
.messages-thread-offer em {
|
||||||
color: #5f748a;
|
color: #5f748a;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
@@ -16888,11 +16879,16 @@ a.city-card:hover {
|
|||||||
|
|
||||||
.messages-chat-user strong {
|
.messages-chat-user strong {
|
||||||
color: #162841;
|
color: #162841;
|
||||||
display: block;
|
display: inline-block;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* "Nazwa - opis" w jednej linii, np. "Piotr Nowak - Sprzedający". */
|
||||||
|
.messages-chat-status-sep {
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
.messages-chat-user small {
|
.messages-chat-user small {
|
||||||
color: #6e8199;
|
color: #6e8199;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
@@ -23369,6 +23365,24 @@ a.city-card:hover {
|
|||||||
width: 18px;
|
width: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Przycisk oka w polu hasla: sam zajmuje miejsce ikony, zeby klikniecie obok rysunku
|
||||||
|
tez przelaczalo widocznosc (wczesniej przycisk mial zerowy rozmiar). */
|
||||||
|
.login-field .login-eye {
|
||||||
|
align-items: center;
|
||||||
|
height: 32px;
|
||||||
|
justify-content: center;
|
||||||
|
position: absolute;
|
||||||
|
right: 4px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-field .login-eye svg {
|
||||||
|
position: static;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
.publish-feedback {
|
.publish-feedback {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
@@ -30635,6 +30649,7 @@ a.city-card:hover {
|
|||||||
@media (max-width: 760px) { .mkt-config-grid { grid-template-columns: 1fr; } .mkt-form-row { grid-template-columns: 1fr; } }
|
@media (max-width: 760px) { .mkt-config-grid { grid-template-columns: 1fr; } .mkt-form-row { grid-template-columns: 1fr; } }
|
||||||
.mkt-config-grid label { display: grid; gap: 6px; font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .03em; color: #6b7f96; }
|
.mkt-config-grid label { display: grid; gap: 6px; font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .03em; color: #6b7f96; }
|
||||||
.mkt-config-grid input, .mkt-config-grid select { padding: 10px 12px; border: 1px solid #d8e3ef; border-radius: 8px; font-size: 13px; font-weight: 500; text-transform: none; letter-spacing: normal; color: #17293c; }
|
.mkt-config-grid input, .mkt-config-grid select { padding: 10px 12px; border: 1px solid #d8e3ef; border-radius: 8px; font-size: 13px; font-weight: 500; text-transform: none; letter-spacing: normal; color: #17293c; }
|
||||||
|
.mkt-config-grid .mkt-field-note { font-size: 12px; font-weight: 500; line-height: 1.4; text-transform: none; letter-spacing: normal; color: #6b7f96; }
|
||||||
.mkt-badge-set { display: inline-flex; align-items: center; gap: 4px; text-transform: none; letter-spacing: normal; font-size: 11px; font-weight: 800; color: #1256a0; background: #e4f0ff; border-radius: 999px; padding: 2px 8px; }
|
.mkt-badge-set { display: inline-flex; align-items: center; gap: 4px; text-transform: none; letter-spacing: normal; font-size: 11px; font-weight: 800; color: #1256a0; background: #e4f0ff; border-radius: 999px; padding: 2px 8px; }
|
||||||
.mkt-badge-set svg { width: 12px; height: 12px; }
|
.mkt-badge-set svg { width: 12px; height: 12px; }
|
||||||
.mkt-config-save { display: flex; justify-content: flex-end; }
|
.mkt-config-save { display: flex; justify-content: flex-end; }
|
||||||
|
|||||||
Reference in New Issue
Block a user