nordabiz/templates/admin/benefits_form.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

354 lines
13 KiB
HTML

{% extends "base.html" %}
{% block title %}{% if benefit %}Edytuj{% else %}Dodaj{% endif %} Korzyść - Admin{% endblock %}
{% block extra_css %}
<style>
.form-container {
max-width: 800px;
margin: 0 auto;
}
.admin-header {
margin-bottom: var(--spacing-xl);
}
.admin-header h1 {
font-size: var(--font-size-2xl);
color: var(--text-primary);
}
.form-section {
background: var(--surface);
padding: var(--spacing-xl);
border-radius: var(--radius-lg);
box-shadow: var(--shadow);
margin-bottom: var(--spacing-lg);
}
.form-section-title {
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--spacing-lg);
padding-bottom: var(--spacing-sm);
border-bottom: 1px solid var(--border);
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--spacing-lg);
margin-bottom: var(--spacing-lg);
}
.form-row.single {
grid-template-columns: 1fr;
}
.form-group {
display: flex;
flex-direction: column;
gap: var(--spacing-xs);
}
.form-group label {
font-weight: 500;
color: var(--text-primary);
font-size: var(--font-size-sm);
}
.form-group label .required {
color: var(--error);
}
.form-group input,
.form-group select,
.form-group textarea {
padding: var(--spacing-sm) var(--spacing-md);
border: 1px solid var(--border);
border-radius: var(--radius-md);
font-size: var(--font-size-md);
background: var(--surface);
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px var(--primary-bg);
}
.form-group textarea {
min-height: 100px;
resize: vertical;
}
.form-group .hint {
font-size: var(--font-size-xs);
color: var(--text-secondary);
}
.checkbox-group {
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.checkbox-group input[type="checkbox"] {
width: 18px;
height: 18px;
}
.form-actions {
display: flex;
gap: var(--spacing-md);
justify-content: flex-end;
padding-top: var(--spacing-lg);
border-top: 1px solid var(--border);
margin-top: var(--spacing-lg);
}
.btn {
padding: var(--spacing-sm) var(--spacing-lg);
border-radius: var(--radius-md);
font-weight: 600;
cursor: pointer;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: var(--spacing-xs);
}
.btn-primary {
background: var(--primary);
color: white;
border: none;
}
.btn-primary:hover {
background: var(--primary-dark);
}
.btn-secondary {
background: var(--surface);
color: var(--text-primary);
border: 1px solid var(--border);
}
.btn-secondary:hover {
background: var(--background);
}
.back-link {
display: inline-flex;
align-items: center;
gap: var(--spacing-xs);
color: var(--text-secondary);
margin-bottom: var(--spacing-lg);
text-decoration: none;
}
.back-link:hover {
color: var(--primary);
}
.back-link svg {
width: 16px;
height: 16px;
}
</style>
{% endblock %}
{% block content %}
<div class="container">
<div class="form-container">
<a href="{{ url_for('admin.admin_benefits') }}" class="back-link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="19" y1="12" x2="5" y2="12"></line>
<polyline points="12 19 5 12 12 5"></polyline>
</svg>
Powrót do listy
</a>
<div class="admin-header">
<h1>{% if benefit %}Edytuj korzyść: {{ benefit.name }}{% else %}Dodaj nową korzyść{% endif %}</h1>
</div>
<form method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<!-- Podstawowe info -->
<div class="form-section">
<div class="form-section-title">Podstawowe informacje</div>
<div class="form-row">
<div class="form-group">
<label>Nazwa <span class="required">*</span></label>
<input type="text" name="name" value="{{ benefit.name if benefit else '' }}" required>
</div>
<div class="form-group">
<label>Slug (URL) <span class="required">*</span></label>
<input type="text" name="slug" value="{{ benefit.slug if benefit else '' }}" required pattern="[a-z0-9-]+">
<span class="hint">Tylko małe litery, cyfry i myślniki, np. wispr-flow</span>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Kategoria</label>
<select name="category">
<option value="">-- Wybierz --</option>
{% for value, label in categories %}
<option value="{{ value }}" {% if benefit and benefit.category == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label>Kolejność wyświetlania</label>
<input type="number" name="display_order" value="{{ benefit.display_order if benefit else 0 }}" min="0">
</div>
</div>
<div class="form-row single">
<div class="form-group">
<label>Krótki opis (na kartę)</label>
<input type="text" name="short_description" value="{{ benefit.short_description if benefit else '' }}" maxlength="200">
<span class="hint">Max 200 znaków</span>
</div>
</div>
<div class="form-row single">
<div class="form-group">
<label>Pełny opis</label>
<textarea name="description">{{ benefit.description if benefit else '' }}</textarea>
</div>
</div>
</div>
<!-- Ceny i oferta -->
<div class="form-section">
<div class="form-section-title">Ceny i oferta</div>
<div class="form-row">
<div class="form-group">
<label>Cena regularna</label>
<input type="text" name="regular_price" value="{{ benefit.regular_price if benefit else '' }}" placeholder="np. $10/mies">
</div>
<div class="form-group">
<label>Cena dla członków</label>
<input type="text" name="member_price" value="{{ benefit.member_price if benefit else '' }}" placeholder="np. $8/mies">
</div>
</div>
<div class="form-row single">
<div class="form-group">
<label>Opis zniżki</label>
<input type="text" name="discount_description" value="{{ benefit.discount_description if benefit else '' }}" placeholder="np. 10% zniżki dla członków">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Kod promocyjny</label>
<input type="text" name="promo_code" value="{{ benefit.promo_code if benefit else '' }}" placeholder="np. NORDA10">
</div>
<div class="form-group">
<label>Instrukcja użycia kodu</label>
<input type="text" name="promo_code_instructions" value="{{ benefit.promo_code_instructions if benefit else '' }}">
</div>
</div>
</div>
<!-- Linki -->
<div class="form-section">
<div class="form-section-title">Linki</div>
<div class="form-row single">
<div class="form-group">
<label>Link afiliacyjny <span class="required">*</span></label>
<input type="url" name="affiliate_url" value="{{ benefit.affiliate_url if benefit else '' }}" required>
<span class="hint">Link przez który śledzimy polecenia</span>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Link do strony produktu</label>
<input type="url" name="product_url" value="{{ benefit.product_url if benefit else '' }}">
</div>
<div class="form-group">
<label>URL logo</label>
<input type="url" name="logo_url" value="{{ benefit.logo_url if benefit else '' }}">
</div>
</div>
</div>
{% if current_user.email == 'maciej.pienczyn@inpi.pl' %}
<!-- Prowizja (tylko dla właściciela) -->
<div class="form-section" style="background: #fffbeb; border: 1px solid #fde68a;">
<div class="form-section-title" style="color: #92400e;">
Dane prowizji (prywatne)
<span style="font-size: var(--font-size-xs); font-weight: normal; margin-left: 8px;">
<svg style="width: 14px; height: 14px; vertical-align: middle;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
</svg>
Widoczne tylko dla Ciebie
</span>
</div>
<div class="form-row">
<div class="form-group">
<label>Wysokość prowizji</label>
<input type="text" name="commission_rate" value="{{ benefit.commission_rate if benefit else '' }}" placeholder="np. 25%">
</div>
<div class="form-group">
<label>Okres prowizji</label>
<input type="text" name="commission_duration" value="{{ benefit.commission_duration if benefit else '' }}" placeholder="np. 12 miesięcy">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Platforma partnerska</label>
<input type="text" name="partner_platform" value="{{ benefit.partner_platform if benefit else '' }}" placeholder="np. Dub Partners">
</div>
<div class="form-group">
<label>Partner od</label>
<input type="date" name="partner_since" value="{{ benefit.partner_since.strftime('%Y-%m-%d') if benefit and benefit.partner_since else '' }}">
</div>
</div>
</div>
{% endif %}
<!-- Status -->
<div class="form-section">
<div class="form-section-title">Status</div>
<div class="form-row">
<div class="form-group">
<div class="checkbox-group">
<input type="checkbox" name="is_active" id="is_active" {% if not benefit or benefit.is_active %}checked{% endif %}>
<label for="is_active">Aktywna (widoczna na stronie)</label>
</div>
</div>
<div class="form-group">
<div class="checkbox-group">
<input type="checkbox" name="is_featured" id="is_featured" {% if benefit and benefit.is_featured %}checked{% endif %}>
<label for="is_featured">Polecana (wyróżniona)</label>
</div>
</div>
</div>
<div class="form-actions">
<a href="{{ url_for('admin.admin_benefits') }}" class="btn btn-secondary">Anuluj</a>
<button type="submit" class="btn btn-primary">
{% if benefit %}Zapisz zmiany{% else %}Dodaj korzyść{% endif %}
</button>
</div>
</div>
</form>
</div>
</div>
{% endblock %}