nordabiz/templates/membership/data_request.html
Maciej Pienczyn e718d96a7d
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
fix(security): Resolve 1 HIGH and 7 MEDIUM vulnerabilities from code review
- HIGH: Fix SQL injection in ZOPK knowledge service (3 functions) — replace f-strings with parameterized queries
- MEDIUM: Sanitize tsquery/LIKE input in SearchService to prevent injection
- MEDIUM: Add @login_required + @role_required(ADMIN) to /health/full endpoint
- MEDIUM: Add @role_required(ADMIN) to ZOPK knowledge search API
- MEDIUM: Add bleach HTML sanitization on write for announcements, events, board proceedings (stored XSS via |safe)
- MEDIUM: Remove partial API key from Gemini service logs
- MEDIUM: Remove @csrf.exempt from chat endpoints, add X-CSRFToken headers in JS
- MEDIUM: Add missing CSRF tokens to 3 POST forms (data_request, benefits_form, benefits_list)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-06 05:25:18 +01:00

367 lines
11 KiB
HTML

{% extends "base.html" %}
{% block title %}Uzupełnij Dane Firmy - Norda Biznes Partner{% endblock %}
{% block extra_css %}
<style>
.request-container {
max-width: 600px;
margin: 0 auto;
}
.request-header {
margin-bottom: var(--spacing-xl);
text-align: center;
}
.request-header h1 {
font-size: var(--font-size-2xl);
color: var(--text-primary);
margin: 0 0 var(--spacing-sm) 0;
}
.company-info {
background: var(--surface);
padding: var(--spacing-xl);
border-radius: var(--radius-lg);
box-shadow: var(--shadow);
margin-bottom: var(--spacing-xl);
}
.company-name {
font-size: var(--font-size-xl);
font-weight: 600;
margin: 0 0 var(--spacing-sm) 0;
}
.company-meta {
color: var(--text-secondary);
font-size: var(--font-size-sm);
}
.missing-data {
background: var(--warning-light);
border: 1px solid var(--warning);
padding: var(--spacing-md);
border-radius: var(--radius);
margin-top: var(--spacing-md);
}
.missing-data h4 {
margin: 0 0 var(--spacing-xs) 0;
color: var(--warning);
font-size: var(--font-size-sm);
}
.missing-data ul {
margin: 0;
padding-left: var(--spacing-lg);
color: var(--text-secondary);
font-size: var(--font-size-sm);
}
.form-section {
background: var(--surface);
padding: var(--spacing-xl);
border-radius: var(--radius-lg);
box-shadow: var(--shadow);
}
.form-section h2 {
font-size: var(--font-size-lg);
margin: 0 0 var(--spacing-lg) 0;
color: var(--text-primary);
border-bottom: 2px solid var(--border);
padding-bottom: var(--spacing-sm);
}
.form-group {
margin-bottom: var(--spacing-lg);
}
.form-group label {
display: block;
font-weight: 500;
margin-bottom: var(--spacing-xs);
}
.form-control {
width: 100%;
padding: var(--spacing-sm) var(--spacing-md);
border: 1px solid var(--border);
border-radius: var(--radius);
font-size: var(--font-size-base);
}
.form-control:focus {
outline: none;
border-color: var(--primary);
}
.nip-lookup {
display: flex;
gap: var(--spacing-sm);
}
.nip-lookup .form-control {
flex: 1;
}
.btn-lookup {
padding: var(--spacing-sm) var(--spacing-lg);
background: var(--surface);
border: 1px solid var(--primary);
color: var(--primary);
border-radius: var(--radius);
cursor: pointer;
font-weight: 500;
white-space: nowrap;
}
.btn-lookup:hover {
background: var(--primary);
color: white;
}
.btn-lookup:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.registry-preview {
background: var(--background);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: var(--spacing-lg);
margin-top: var(--spacing-lg);
}
.registry-preview.success {
border-color: var(--success);
background: rgba(var(--success-rgb), 0.05);
}
.registry-preview h4 {
margin: 0 0 var(--spacing-sm) 0;
color: var(--success);
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.registry-data {
display: grid;
gap: var(--spacing-xs);
font-size: var(--font-size-sm);
}
.btn-submit {
width: 100%;
padding: var(--spacing-md);
background: var(--primary);
color: white;
border: none;
border-radius: var(--radius);
font-size: var(--font-size-base);
font-weight: 500;
cursor: pointer;
margin-top: var(--spacing-lg);
}
.btn-submit:hover {
opacity: 0.9;
}
.btn-submit:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.pending-alert {
background: var(--warning-light);
border: 1px solid var(--warning);
padding: var(--spacing-lg);
border-radius: var(--radius);
text-align: center;
}
.pending-alert h3 {
margin: 0 0 var(--spacing-sm) 0;
color: var(--warning);
}
.loading-spinner {
display: inline-block;
width: 16px;
height: 16px;
border: 2px solid currentColor;
border-top-color: transparent;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
</style>
{% endblock %}
{% block content %}
<div class="request-container">
<div class="request-header">
<h1>Uzupełnij Dane Firmy</h1>
<p class="text-muted">Pobierz aktualne dane z rejestru KRS lub CEIDG</p>
</div>
<div class="company-info">
<h2 class="company-name">{{ company.name }}</h2>
<div class="company-meta">
{% if company.nip %}NIP: {{ company.nip }}{% else %}Brak NIP{% endif %}
{% if company.address_city %} • {{ company.address_city }}{% endif %}
</div>
{% set missing = [] %}
{% if not company.nip %}{% set _ = missing.append('NIP') %}{% endif %}
{% if not company.regon %}{% set _ = missing.append('REGON') %}{% endif %}
{% if not company.address_postal %}{% set _ = missing.append('Kod pocztowy') %}{% endif %}
{% if not company.address_city %}{% set _ = missing.append('Miasto') %}{% endif %}
{% if not company.website %}{% set _ = missing.append('Strona WWW') %}{% endif %}
{% if missing %}
<div class="missing-data">
<h4>Brakujące dane:</h4>
<ul>
{% for field in missing %}
<li>{{ field }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
</div>
{% if pending_request %}
<div class="pending-alert">
<h3>Masz już oczekujące zgłoszenie</h3>
<p>Twoje poprzednie zgłoszenie z dnia {{ pending_request.created_at.strftime('%Y-%m-%d') }} jest w trakcie rozpatrywania.</p>
<p><a href="{{ url_for('index') }}">Wróć na stronę główną</a></p>
</div>
{% else %}
<div class="form-section">
<h2>Wprowadź NIP firmy</h2>
<form method="POST" id="dataRequestForm">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="form-group">
<label>NIP firmy</label>
<div class="nip-lookup">
<input type="text" class="form-control" id="nipInput" name="nip"
value="{{ company.nip or '' }}"
placeholder="0000000000" maxlength="10" pattern="\d{10}" required>
<button type="button" class="btn-lookup" id="btnLookup">
Sprawdź
</button>
</div>
</div>
<div id="registryPreview" class="registry-preview" style="display: none;">
<h4>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
<polyline points="22,4 12,14.01 9,11.01"/>
</svg>
Dane z rejestru <span id="registrySource">KRS</span>
</h4>
<div class="registry-data" id="registryData"></div>
</div>
<div class="form-group">
<label>Notatka (opcjonalnie)</label>
<textarea class="form-control" name="user_note" rows="3"
placeholder="Dodatkowe informacje dla administratora..."></textarea>
</div>
<button type="submit" class="btn-submit" id="btnSubmit" disabled>
Wyślij zgłoszenie do zatwierdzenia
</button>
</form>
</div>
{% endif %}
</div>
{% endblock %}
{% block extra_js %}
{% if not pending_request %}
const nipInput = document.getElementById('nipInput');
const btnLookup = document.getElementById('btnLookup');
const btnSubmit = document.getElementById('btnSubmit');
const registryPreview = document.getElementById('registryPreview');
const registrySource = document.getElementById('registrySource');
const registryData = document.getElementById('registryData');
let hasValidNip = false;
btnLookup.addEventListener('click', async function() {
const nip = nipInput.value.replace(/[\s-]/g, '');
if (nip.length !== 10 || !/^\d+$/.test(nip)) {
alert('NIP musi mieć 10 cyfr');
return;
}
btnLookup.disabled = true;
btnLookup.innerHTML = '<span class="loading-spinner"></span>';
try {
const response = await fetch('/api/membership/lookup-nip', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ nip: nip })
});
const result = await response.json();
if (result.success && result.data) {
registrySource.textContent = result.source;
registryPreview.classList.add('success');
registryPreview.style.display = 'block';
const data = result.data;
registryData.innerHTML = `
<div><strong>Nazwa:</strong> ${data.name || '-'}</div>
<div><strong>Adres:</strong> ${data.address_postal_code || ''} ${data.address_city || ''}</div>
${data.regon ? `<div><strong>REGON:</strong> ${data.regon}</div>` : ''}
${data.krs ? `<div><strong>KRS:</strong> ${data.krs}</div>` : ''}
`;
hasValidNip = true;
btnSubmit.disabled = false;
} else {
registryPreview.classList.remove('success');
registryPreview.style.display = 'block';
registryData.innerHTML = '<p>Firma nie została znaleziona. Sprawdź poprawność NIP.</p>';
hasValidNip = false;
btnSubmit.disabled = true;
}
} catch (error) {
console.error('Lookup error:', error);
alert('Błąd podczas sprawdzania NIP');
} finally {
btnLookup.disabled = false;
btnLookup.innerHTML = 'Sprawdź';
}
});
nipInput.addEventListener('input', function() {
hasValidNip = false;
btnSubmit.disabled = true;
registryPreview.style.display = 'none';
});
document.getElementById('dataRequestForm').addEventListener('submit', function(e) {
if (!hasValidNip) {
e.preventDefault();
alert('Najpierw sprawdź NIP w rejestrze');
}
});
{% endif %}
{% endblock %}