nordabiz/templates/auth/reset_password.html
Maciej Pienczyn cebe52f303 refactor: Rebranding i aktualizacja modelu AI
- Zmiana nazwy: "Norda Biznes Hub" → "Norda Biznes Partner"
- Aktualizacja modelu AI: Gemini 2.0 Flash → Gemini 3 Flash
- Zachowano historyczne odniesienia w timeline i dokumentacji

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 14:08:39 +01:00

380 lines
12 KiB
HTML
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% block title %}Nowe haslo - Norda Biznes Partner{% endblock %}
{% block container_class %}container-narrow{% endblock %}
{% block extra_css %}
<style>
.auth-container {
max-width: 480px;
margin: 0 auto;
padding: var(--spacing-2xl) 0;
}
.auth-card {
background-color: var(--surface);
padding: var(--spacing-2xl);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-lg);
}
.auth-header {
text-align: center;
margin-bottom: var(--spacing-xl);
}
.auth-header h1 {
font-size: var(--font-size-3xl);
color: var(--text-primary);
margin-bottom: var(--spacing-sm);
}
.auth-header p {
color: var(--text-secondary);
}
.auth-icon {
width: 64px;
height: 64px;
background: linear-gradient(135deg, var(--success), #059669);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto var(--spacing-lg);
}
.auth-icon svg {
width: 32px;
height: 32px;
color: white;
}
.form-group {
margin-bottom: var(--spacing-lg);
}
.form-label {
display: block;
font-weight: 500;
margin-bottom: var(--spacing-sm);
color: var(--text-primary);
}
.form-label .required {
color: var(--error);
}
.form-input {
width: 100%;
padding: var(--spacing-md);
border: 1px solid var(--border);
border-radius: var(--radius);
font-size: var(--font-size-base);
font-family: var(--font-family);
transition: var(--transition);
}
.form-input:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
}
.form-input.error {
border-color: var(--error);
}
.password-strength {
margin-top: var(--spacing-sm);
height: 4px;
background-color: var(--border);
border-radius: var(--radius-sm);
overflow: hidden;
}
.password-strength-bar {
height: 100%;
width: 0;
transition: var(--transition);
}
.password-strength-bar.weak {
background-color: var(--error);
width: 33%;
}
.password-strength-bar.medium {
background-color: var(--warning);
width: 66%;
}
.password-strength-bar.strong {
background-color: var(--success);
width: 100%;
}
.password-requirements {
font-size: var(--font-size-sm);
color: var(--text-secondary);
margin-top: var(--spacing-sm);
}
.password-requirements ul {
list-style: none;
padding: 0;
margin: var(--spacing-sm) 0 0 0;
}
.password-requirements li {
padding: var(--spacing-xs) 0;
display: flex;
align-items: center;
gap: var(--spacing-sm);
transition: all 0.2s ease;
}
.password-requirements li .checkbox-icon {
width: 20px;
height: 20px;
border: 2px solid var(--border);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
background: var(--surface);
transition: all 0.2s ease;
flex-shrink: 0;
}
.password-requirements li.valid .checkbox-icon {
background: var(--success);
border-color: var(--success);
}
.password-requirements li.valid .checkbox-icon::after {
content: "\2713";
color: white;
font-weight: bold;
font-size: 14px;
}
.password-requirements li.valid {
color: var(--success);
font-weight: 500;
}
.form-actions {
margin-top: var(--spacing-xl);
}
.btn-full {
width: 100%;
}
.btn-success {
background-color: var(--success);
color: white;
}
.btn-success:hover {
background-color: #059669;
}
.auth-footer {
text-align: center;
margin-top: var(--spacing-lg);
padding-top: var(--spacing-lg);
border-top: 1px solid var(--border);
color: var(--text-secondary);
}
.auth-footer a {
color: var(--primary);
text-decoration: none;
font-weight: 500;
}
.auth-footer a:hover {
text-decoration: underline;
}
</style>
{% endblock %}
{% block content %}
<div class="auth-container">
<div class="auth-card">
<div class="auth-header">
<div class="auth-icon">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/>
</svg>
</div>
<h1>Ustaw nowe haslo</h1>
<p>Wprowadz nowe haslo do swojego konta</p>
</div>
<form method="POST" action="{{ url_for('reset_password', token=token) }}" novalidate>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="form-group">
<label for="password" class="form-label">
Nowe haslo <span class="required">*</span>
</label>
<input
type="password"
id="password"
name="password"
class="form-input"
placeholder="********"
required
autocomplete="new-password"
autofocus
>
<div class="password-strength">
<div class="password-strength-bar" id="strengthBar"></div>
</div>
<div class="password-requirements">
<ul id="requirements">
<li id="req-length">
<span class="checkbox-icon"></span>
<span class="requirement-text">Minimum 8 znakow</span>
</li>
<li id="req-upper">
<span class="checkbox-icon"></span>
<span class="requirement-text">Wielka litera</span>
</li>
<li id="req-lower">
<span class="checkbox-icon"></span>
<span class="requirement-text">Mala litera</span>
</li>
<li id="req-digit">
<span class="checkbox-icon"></span>
<span class="requirement-text">Cyfra</span>
</li>
</ul>
</div>
</div>
<div class="form-group">
<label for="password_confirm" class="form-label">
Potwierdz haslo <span class="required">*</span>
</label>
<input
type="password"
id="password_confirm"
name="password_confirm"
class="form-input"
placeholder="********"
required
autocomplete="new-password"
>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-success btn-lg btn-full" id="submitBtn" disabled>
Zmien haslo
</button>
</div>
</form>
<div class="auth-footer">
<p><a href="{{ url_for('login') }}">Powrot do logowania</a></p>
</div>
</div>
</div>
<div id="toastContainer" style="position: fixed; top: 80px; right: 20px; z-index: 1100; display: flex; flex-direction: column; gap: 10px;"></div>
<style>
.toast { padding: 12px 20px; border-radius: var(--radius); background: var(--surface); border-left: 4px solid var(--primary); box-shadow: 0 4px 12px rgba(0,0,0,0.15); display: flex; align-items: center; gap: 10px; animation: toastIn 0.3s ease; }
.toast.error { border-left-color: var(--error); }
@keyframes toastIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
@keyframes toastOut { from { opacity: 1; } to { opacity: 0; } }
</style>
{% endblock %}
{% block extra_js %}
function showToast(message, type = 'info', duration = 4000) {
const container = document.getElementById('toastContainer');
const icons = { error: '✕', info: '' };
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.innerHTML = `<span style="font-size:1.2em">${icons[type]||''}</span><span>${message}</span>`;
container.appendChild(toast);
setTimeout(() => { toast.style.animation = 'toastOut 0.3s ease forwards'; setTimeout(() => toast.remove(), 300); }, duration);
}
const passwordInput = document.getElementById('password');
const passwordConfirm = document.getElementById('password_confirm');
const strengthBar = document.getElementById('strengthBar');
const submitBtn = document.getElementById('submitBtn');
passwordInput.addEventListener('input', function() {
const password = this.value;
let strength = 0;
let validCount = 0;
const hasLength = password.length >= 8;
const hasUpper = /[A-Z]/.test(password);
const hasLower = /[a-z]/.test(password);
const hasDigit = /\d/.test(password);
updateRequirement('req-length', hasLength);
updateRequirement('req-upper', hasUpper);
updateRequirement('req-lower', hasLower);
updateRequirement('req-digit', hasDigit);
if (hasLength) { strength++; validCount++; }
if (hasUpper) { strength++; validCount++; }
if (hasLower) { strength++; validCount++; }
if (hasDigit) { strength++; validCount++; }
strengthBar.className = 'password-strength-bar';
if (strength === 1 || strength === 2) {
strengthBar.classList.add('weak');
} else if (strength === 3) {
strengthBar.classList.add('medium');
} else if (strength === 4) {
strengthBar.classList.add('strong');
}
checkFormValidity();
});
passwordConfirm.addEventListener('input', checkFormValidity);
function updateRequirement(id, valid) {
const el = document.getElementById(id);
if (valid) {
el.classList.add('valid');
} else {
el.classList.remove('valid');
}
}
function checkFormValidity() {
const password = passwordInput.value;
const confirm = passwordConfirm.value;
const hasLength = password.length >= 8;
const hasUpper = /[A-Z]/.test(password);
const hasLower = /[a-z]/.test(password);
const hasDigit = /\d/.test(password);
const passwordsMatch = password === confirm && confirm.length > 0;
submitBtn.disabled = !(hasLength && hasUpper && hasLower && hasDigit && passwordsMatch);
}
document.querySelector('form').addEventListener('submit', function(e) {
const password = passwordInput.value;
const confirm = passwordConfirm.value;
if (password !== confirm) {
passwordConfirm.classList.add('error');
e.preventDefault();
showToast('Hasła nie są identyczne', 'error');
}
});
{% endblock %}