feat: add background toggle to logo selection modal
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions

When choosing a logo, admins can now switch between light and dark
backgrounds to see which works better for transparent logos. The
selected background preference is automatically saved when confirming.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-02-20 20:14:25 +01:00
parent d9d0f184d1
commit 0d4eb034a0
3 changed files with 120 additions and 4 deletions

View File

@ -644,8 +644,12 @@ def api_fetch_company_logo(company_id):
if action == 'confirm':
index = data.get('index', 0)
dark_bg = data.get('dark_bg', False)
ok = service.confirm_candidate(company.slug, index)
logger.info(f"Logo candidate #{index} confirmed for {company.name} by {current_user.email}")
if ok:
company.logo_dark_bg = dark_bg
db.commit()
logger.info(f"Logo candidate #{index} confirmed for {company.name} (dark_bg={dark_bg}) by {current_user.email}")
return jsonify({'success': ok, 'message': 'Logo zapisane' if ok else 'Nie znaleziono kandydata'})
if action == 'cancel':

View File

@ -399,6 +399,34 @@
.logo-candidate:hover { border-color: var(--primary); transform: scale(1.05); }
.logo-candidate.recommended { border-color: var(--success); border-width: 3px; }
.logo-candidates-grid.dark-preview .logo-candidate {
background: #1f2937;
border-color: #374151;
}
.logo-candidates-grid.dark-preview .logo-candidate.recommended {
border-color: var(--success);
}
.logo-bg-toggle-inline {
display: inline-flex;
gap: 4px;
margin-left: var(--spacing-sm);
vertical-align: middle;
}
.logo-bg-toggle-inline button {
padding: 3px 10px;
border: 1px solid var(--border);
border-radius: var(--radius);
font-size: 11px;
cursor: pointer;
background: white;
color: var(--text-secondary);
transition: all 0.2s ease;
}
.logo-bg-toggle-inline button.active {
border-color: #6366f1;
background: #eef2ff;
color: #4338ca;
}
.logo-candidate img {
max-width: 100%;
@ -917,11 +945,31 @@
.finally(function() { btn.disabled = false; });
}
var logoCandidatesDarkBg = false;
function toggleCandidatesBg(dark) {
logoCandidatesDarkBg = dark;
var grid = document.querySelector('.logo-candidates-grid');
var btnLight = document.getElementById('candidatesBgLight');
var btnDark = document.getElementById('candidatesBgDark');
if (!grid) return;
if (dark) {
grid.classList.add('dark-preview');
if (btnDark) btnDark.classList.add('active');
if (btnLight) btnLight.classList.remove('active');
} else {
grid.classList.remove('dark-preview');
if (btnLight) btnLight.classList.add('active');
if (btnDark) btnDark.classList.remove('active');
}
}
function fetchLogo() {
var btn = document.getElementById('btn-logo');
var original = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<span class="spinner"></span> Szukam...';
logoCandidatesDarkBg = false;
return fetch('/api/company/{{ company.id }}/fetch-logo', {
method: 'POST',
@ -939,7 +987,11 @@
}
var container = document.getElementById('logo-candidates');
var html = '<div class="logo-candidates-label">Wybierz logo (' + data.candidates.length + ' kandydatów):</div>';
var html = '<div class="logo-candidates-label">Wybierz logo (' + data.candidates.length + ' kandydatów):';
html += '<span class="logo-bg-toggle-inline">';
html += '<button id="candidatesBgLight" class="active" onclick="toggleCandidatesBg(false)">Jasne</button>';
html += '<button id="candidatesBgDark" onclick="toggleCandidatesBg(true)">Ciemne</button>';
html += '</span></div>';
html += '<div class="logo-candidates-grid">';
data.candidates.forEach(function(c) {
var imgUrl = '/static/img/companies/' + c.filename + '?t=' + Date.now();
@ -977,7 +1029,7 @@
fetch('/api/company/{{ company.id }}/fetch-logo', {
method: 'POST',
headers: {'Content-Type': 'application/json', 'X-CSRFToken': csrfToken},
body: JSON.stringify({action: 'confirm', index: index})
body: JSON.stringify({action: 'confirm', index: index, dark_bg: logoCandidatesDarkBg})
})
.then(function(r) { return r.json(); })
.then(function(data) {

View File

@ -448,6 +448,37 @@
justify-content: center;
overflow: hidden;
border: 1px solid #e5e7eb;
transition: background 0.3s ease;
}
.logo-gallery-grid.dark-preview .gallery-img {
background: #1f2937;
border-color: #374151;
}
.logo-bg-toggle {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-bottom: var(--spacing-md);
}
.logo-bg-toggle button {
padding: 6px 14px;
border: 2px solid var(--border);
border-radius: var(--radius);
font-size: var(--font-size-sm);
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
background: white;
color: var(--text-secondary);
}
.logo-bg-toggle button.active {
border-color: #6366f1;
background: #eef2ff;
color: #4338ca;
}
.logo-bg-toggle button:hover:not(.active) {
border-color: #9ca3af;
}
.logo-gallery-item .gallery-img img {
max-width: 90%;
@ -4267,6 +4298,16 @@
<div class="logo-gallery-content">
<h3>Wybierz logo</h3>
<p class="gallery-subtitle">Kliknij na logo, które chcesz użyć dla firmy</p>
<div class="logo-bg-toggle">
<button id="logoBgLight" class="active" onclick="setGalleryBg(false)">
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" style="vertical-align:-2px"><path d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"/></svg>
Jasne tło
</button>
<button id="logoBgDark" onclick="setGalleryBg(true)">
<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" style="vertical-align:-2px"><path d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"/></svg>
Ciemne tło
</button>
</div>
<div class="logo-gallery-grid" id="logoGalleryGrid">
<!-- Dynamically populated by JS -->
</div>
@ -4853,6 +4894,24 @@ function updateLogoStep(stepId, status, message) {
return resp.json();
}
let galleryDarkBg = false;
window.setGalleryBg = function(dark) {
galleryDarkBg = dark;
const grid = document.getElementById('logoGalleryGrid');
const btnLight = document.getElementById('logoBgLight');
const btnDark = document.getElementById('logoBgDark');
if (dark) {
grid.classList.add('dark-preview');
btnDark.classList.add('active');
btnLight.classList.remove('active');
} else {
grid.classList.remove('dark-preview');
btnLight.classList.add('active');
btnDark.classList.remove('active');
}
};
function buildGallery(candidates, existingLogoExt, recommendedIndex) {
const grid = document.getElementById('logoGalleryGrid');
grid.innerHTML = '';
@ -4937,6 +4996,7 @@ function updateLogoStep(stepId, status, message) {
// Show gallery with all candidates
buildGallery(data.candidates, data.existing_logo_ext, data.recommended_index);
setGalleryBg(false); // Reset to light background
document.getElementById('logoGalleryOverlay').classList.add('active');
} catch (error) {
@ -4962,7 +5022,7 @@ function updateLogoStep(stepId, status, message) {
if (selectedIndex === null) return;
this.disabled = true;
this.textContent = 'Zapisuję...';
await sendLogoAction('confirm', { index: selectedIndex });
await sendLogoAction('confirm', { index: selectedIndex, dark_bg: galleryDarkBg });
document.getElementById('logoGalleryOverlay').classList.remove('active');
showToast('Logo zapisane!', 'success', 3000);
await sleep(2000);