also looking at this
fix: MVP-readiness fixes — restore lost UI feedback, register safety, analytics error state, mobile + a11y
#31
7 files
+123
-29
| @@ -51,6 +51,10 @@ | ||
| 51 | 51 | <span class="font-bold text-xs text-zinc-500">Password</span> |
| 52 | 52 | <input class="px-3 h-10 text-neutral-950 text-sm bg-white border border-zinc-200 outline-none rounded-xl focus:border-blue-500 focus:ring-2 focus:ring-blue-100" name="password" type="password" autocomplete="current-password" placeholder="••••••••" required minlength="6" data-login-password> |
| 53 | 53 | </label> |
| 54 | <label class="hidden gap-1.5" data-login-confirm-row> | |
| 55 | <span class="font-bold text-xs text-zinc-500">Confirm password</span> | |
| 56 | <input class="px-3 h-10 text-neutral-950 text-sm bg-white border border-zinc-200 outline-none rounded-xl focus:border-blue-500 focus:ring-2 focus:ring-blue-100" name="password_confirmation" type="password" autocomplete="new-password" placeholder="••••••••" minlength="6" data-login-confirm> | |
| 57 | </label> | |
| 54 | 58 | <p class="hidden m-0 text-red-700 text-xs" data-login-error></p> |
| 55 | 59 | <button class="inline-flex items-center justify-center px-4 h-10 font-bold text-sm text-white bg-neutral-950 hover:bg-neutral-800 rounded-xl disabled:opacity-50 transition-colors" type="submit" data-login-submit>Sign in</button> |
| 56 | 60 | </form> |
| @@ -71,6 +75,8 @@ | ||
| 71 | 75 | const nameInput = root.querySelector('[data-login-name]') |
| 72 | 76 | const emailInput = root.querySelector('[data-login-email]') |
| 73 | 77 | const passwordInput = root.querySelector('[data-login-password]') |
| 78 | const confirmRow = root.querySelector('[data-login-confirm-row]') | |
| 79 | const confirmInput = root.querySelector('[data-login-confirm]') | |
| 74 | 80 | const errorText = root.querySelector('[data-login-error]') |
| 75 | 81 | const submit = root.querySelector('[data-login-submit]') |
| 76 | 82 | const toggle = root.querySelector('[data-login-toggle]') |
| @@ -87,6 +93,12 @@ | ||
| 87 | 93 | registering = register |
| 88 | 94 | nameRow?.classList.toggle('hidden', !register) |
| 89 | 95 | nameRow?.classList.toggle('grid', register) |
| 96 | confirmRow?.classList.toggle('hidden', !register) | |
| 97 | confirmRow?.classList.toggle('grid', register) | |
| 98 | // The workspace-owning account has no recovery path, so a first-run | |
| 99 | // typo must not silently create an account nobody can sign into. | |
| 100 | nameInput?.toggleAttribute('required', register) | |
| 101 | confirmInput?.toggleAttribute('required', register) | |
| 90 | 102 | if (heading) heading.textContent = register ? 'Create your workspace' : 'Sign in to Postline' |
| 91 | 103 | if (subtitle) subtitle.textContent = register ? 'One account owns this Postline instance.' : 'Your personal publishing workspace.' |
| 92 | 104 | if (submit) submit.textContent = register ? 'Create account' : 'Sign in' |
| @@ -105,6 +117,12 @@ | ||
| 105 | 117 | form?.addEventListener('submit', async (event) => { |
| 106 | 118 | event.preventDefault() |
| 107 | 119 | setError('') |
| 120 | ||
| 121 | if (registering && (passwordInput?.value || '') !== (confirmInput?.value || '')) { | |
| 122 | setError('Passwords do not match.') | |
| 123 | return | |
| 124 | } | |
| 125 | ||
| 108 | 126 | submit?.setAttribute('disabled', 'disabled') |
| 109 | 127 | |
| 110 | 128 | const body = new FormData() |