ReviewOS

also looking at this

stacks/postline

fix: MVP-readiness fixes — restore lost UI feedback, register safety, analytics error state, mobile + a11y

#31
Merged glennmichael123 wants to merge fix/readiness-batch-1-feedback-timing into main
7 files +123 -29
resources/views/login.stxmodified+18-0
Changes to resources/views/login.stx
@@ -51,6 +51,10 @@
5151 <span class="font-bold text-xs text-zinc-500">Password</span>
5252 <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>
5353 </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>
5458 <p class="hidden m-0 text-red-700 text-xs" data-login-error></p>
5559 <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>
5660 </form>
@@ -71,6 +75,8 @@
7175 const nameInput = root.querySelector('[data-login-name]')
7276 const emailInput = root.querySelector('[data-login-email]')
7377 const passwordInput = root.querySelector('[data-login-password]')
78 const confirmRow = root.querySelector('[data-login-confirm-row]')
79 const confirmInput = root.querySelector('[data-login-confirm]')
7480 const errorText = root.querySelector('[data-login-error]')
7581 const submit = root.querySelector('[data-login-submit]')
7682 const toggle = root.querySelector('[data-login-toggle]')
@@ -87,6 +93,12 @@
8793 registering = register
8894 nameRow?.classList.toggle('hidden', !register)
8995 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)
90102 if (heading) heading.textContent = register ? 'Create your workspace' : 'Sign in to Postline'
91103 if (subtitle) subtitle.textContent = register ? 'One account owns this Postline instance.' : 'Your personal publishing workspace.'
92104 if (submit) submit.textContent = register ? 'Create account' : 'Sign in'
@@ -105,6 +117,12 @@
105117 form?.addEventListener('submit', async (event) => {
106118 event.preventDefault()
107119 setError('')
120
121 if (registering && (passwordInput?.value || '') !== (confirmInput?.value || '')) {
122 setError('Passwords do not match.')
123 return
124 }
125
108126 submit?.setAttribute('disabled', 'disabled')
109127
110128 const body = new FormData()