feat: orientation model, password confirm, frontend build
- Collapse orientation to landscape/portrait (ribbon left = portrait standard) - Add OrientationPicker component and wire settings sheet in HomeView - Add password confirmation field to registration form (RepeatedType) - Build frontend SPA to public/build/ Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -90,14 +90,27 @@
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
{{ form_label(form.plainPassword) }}
|
||||
{{ form_widget(form.plainPassword, {attr: {
|
||||
{{ form_label(form.plainPassword.first) }}
|
||||
{{ form_widget(form.plainPassword.first, {attr: {
|
||||
id: 'reg-password',
|
||||
autocomplete: 'new-password',
|
||||
'aria-describedby': 'reg-password-error',
|
||||
'aria-invalid': form.plainPassword.vars.errors|length > 0 ? 'true' : 'false'
|
||||
'aria-invalid': form.plainPassword.first.vars.errors|length > 0 ? 'true' : 'false'
|
||||
}}) }}
|
||||
<p id="reg-password-error" class="field-error" role="alert">
|
||||
{% for error in form.plainPassword.first.vars.errors %}{{ error.message }}{% endfor %}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
{{ form_label(form.plainPassword.second) }}
|
||||
{{ form_widget(form.plainPassword.second, {attr: {
|
||||
id: 'reg-password-confirm',
|
||||
autocomplete: 'new-password',
|
||||
'aria-describedby': 'reg-password-confirm-error',
|
||||
'aria-invalid': form.plainPassword.vars.errors|length > 0 ? 'true' : 'false'
|
||||
}}) }}
|
||||
<p id="reg-password-confirm-error" class="field-error" role="alert">
|
||||
{% for error in form.plainPassword.vars.errors %}{{ error.message }}{% endfor %}
|
||||
</p>
|
||||
</div>
|
||||
@@ -116,9 +129,10 @@
|
||||
var emailError = document.getElementById('reg-email-error');
|
||||
var pwInput = document.getElementById('reg-password');
|
||||
var pwError = document.getElementById('reg-password-error');
|
||||
var confirmInput = document.getElementById('reg-password-confirm');
|
||||
var confirmError = document.getElementById('reg-password-confirm-error');
|
||||
|
||||
function validateEmail() {
|
||||
// Only run client-side validation when there is no server-side error already shown
|
||||
if (emailError.dataset.serverError) return;
|
||||
var val = emailInput.value.trim();
|
||||
if (!val) {
|
||||
@@ -142,6 +156,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
function validateConfirm() {
|
||||
if (confirmError.dataset.serverError) return;
|
||||
if (confirmInput.value && confirmInput.value !== pwInput.value) {
|
||||
setError(confirmInput, confirmError, 'Passwords do not match.');
|
||||
} else {
|
||||
clearError(confirmInput, confirmError);
|
||||
}
|
||||
}
|
||||
|
||||
function setError(input, errorEl, message) {
|
||||
input.setAttribute('aria-invalid', 'true');
|
||||
errorEl.textContent = message;
|
||||
@@ -155,17 +178,23 @@
|
||||
// Mark existing server errors so client-side blur doesn't clobber them
|
||||
if (emailError.textContent.trim()) emailError.dataset.serverError = '1';
|
||||
if (pwError.textContent.trim()) pwError.dataset.serverError = '1';
|
||||
if (confirmError.textContent.trim()) confirmError.dataset.serverError = '1';
|
||||
|
||||
// Clear server-error flag once user starts typing
|
||||
emailInput.addEventListener('input', function () { delete emailError.dataset.serverError; });
|
||||
pwInput.addEventListener('input', function () { delete pwError.dataset.serverError; });
|
||||
confirmInput.addEventListener('input', function () { delete confirmError.dataset.serverError; });
|
||||
|
||||
emailInput.addEventListener('blur', validateEmail);
|
||||
pwInput.addEventListener('blur', validatePassword);
|
||||
confirmInput.addEventListener('blur', validateConfirm);
|
||||
// Re-validate confirm whenever the password field changes
|
||||
pwInput.addEventListener('input', function () { if (confirmInput.value) validateConfirm(); });
|
||||
|
||||
form.addEventListener('submit', function () {
|
||||
form.addEventListener('submit', function (e) {
|
||||
validateEmail();
|
||||
validatePassword();
|
||||
validateConfirm();
|
||||
});
|
||||
}());
|
||||
</script>
|
||||
|
||||
@@ -47,8 +47,8 @@
|
||||
<div class="field">
|
||||
<label for="orientation">Display orientation</label>
|
||||
<select id="orientation" name="orientation">
|
||||
<option value="landscape" {% if device.orientation.value == 'landscape' %}selected{% endif %}>Landscape</option>
|
||||
<option value="portrait" {% if device.orientation.value == 'portrait' %}selected{% endif %}>Portrait</option>
|
||||
<option value="landscape" {% if device.orientation.value == 'landscape' %}selected{% endif %}>Landscape — ribbon at bottom</option>
|
||||
<option value="portrait" {% if device.orientation.value == 'portrait' %}selected{% endif %}>Portrait — ribbon on left</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user