also looking at this
fix: MVP-readiness fixes — restore lost UI feedback, register safety, analytics error state, mobile + a11y
#31
7 files
+123
-29
| @@ -37,7 +37,7 @@ | ||
| 37 | 37 | <span class="text-xs text-zinc-500" data-bluesky-channel-pill>Loading…</span> |
| 38 | 38 | </header> |
| 39 | 39 | <div class="grid gap-2.5 p-3" data-bluesky-channels> |
| 40 | <article class="flex gap-5 items-center justify-between p-3.5 bg-white border border-zinc-200 rounded-xl" data-channel="bluesky"> | |
| 40 | <article class="flex flex-col gap-3 justify-between sm:flex-row sm:gap-5 sm:items-center p-3.5 bg-white border border-zinc-200 rounded-xl" data-channel="bluesky"> | |
| 41 | 41 | <div> |
| 42 | 42 | <h2 class="my-0 font-bold text-[15px]">Bluesky</h2> |
| 43 | 43 | <p class="my-1.5 text-[13px] text-slate-700" data-bluesky-channel-handle>Not connected</p> |
| @@ -45,7 +45,7 @@ | ||
| 45 | 45 | </div> |
| 46 | 46 | <span class="px-2.5 py-1 font-bold text-[11px] text-zinc-500 whitespace-nowrap border border-zinc-200 rounded-full" data-bluesky-channel-status>Setup</span> |
| 47 | 47 | </article> |
| 48 | <article class="flex gap-5 items-center justify-between p-3.5 bg-white border border-zinc-200 rounded-xl" data-channel="linkedin"> | |
| 48 | <article class="flex flex-col gap-3 justify-between sm:flex-row sm:gap-5 sm:items-center p-3.5 bg-white border border-zinc-200 rounded-xl" data-channel="linkedin"> | |
| 49 | 49 | <div> |
| 50 | 50 | <h2 class="my-0 font-bold text-[15px]">LinkedIn</h2> |
| 51 | 51 | <p class="my-1.5 text-[13px] text-slate-700" data-linkedin-channel-handle>Not connected</p> |
| @@ -57,7 +57,7 @@ | ||
| 57 | 57 | <button class="hidden text-xs text-zinc-500 underline hover:text-zinc-700 transition-colors" type="button" data-linkedin-connect-env>Use token from .env</button> |
| 58 | 58 | </div> |
| 59 | 59 | </article> |
| 60 | <article class="flex gap-5 items-center justify-between p-3.5 bg-white border border-zinc-200 rounded-xl" data-channel="instagram"> | |
| 60 | <article class="flex flex-col gap-3 justify-between sm:flex-row sm:gap-5 sm:items-center p-3.5 bg-white border border-zinc-200 rounded-xl" data-channel="instagram"> | |
| 61 | 61 | <div> |
| 62 | 62 | <h2 class="my-0 font-bold text-[15px]">Instagram</h2> |
| 63 | 63 | <p class="my-1.5 text-[13px] text-slate-700" data-instagram-channel-handle>Not connected</p> |
| @@ -69,7 +69,7 @@ | ||
| 69 | 69 | <button class="hidden text-xs text-zinc-500 underline hover:text-zinc-700 transition-colors" type="button" data-instagram-connect-env>Use token from .env</button> |
| 70 | 70 | </div> |
| 71 | 71 | </article> |
| 72 | <article class="flex gap-5 items-center justify-between p-3.5 bg-white border border-zinc-200 rounded-xl" data-channel="threads"> | |
| 72 | <article class="flex flex-col gap-3 justify-between sm:flex-row sm:gap-5 sm:items-center p-3.5 bg-white border border-zinc-200 rounded-xl" data-channel="threads"> | |
| 73 | 73 | <div> |
| 74 | 74 | <h2 class="my-0 font-bold text-[15px]">Threads</h2> |
| 75 | 75 | <p class="my-1.5 text-[13px] text-slate-700" data-threads-channel-handle>Not connected</p> |
| @@ -91,13 +91,13 @@ | ||
| 91 | 91 | <span class="px-2.5 py-1 font-bold text-[11px] text-zinc-500 whitespace-nowrap border border-zinc-200 rounded-full" data-mastodon-channel-status>Setup</span> |
| 92 | 92 | </div> |
| 93 | 93 | <form class="grid gap-2 sm:grid-cols-[minmax(0,1fr)_minmax(0,1.4fr)_auto]" data-mastodon-connect-form> |
| 94 | <input class="px-3 h-9 text-neutral-950 text-sm bg-white border border-zinc-200 outline-none rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-100" name="instance" placeholder="mastodon.social" autocomplete="url" data-mastodon-instance required> | |
| 95 | <input class="px-3 h-9 text-neutral-950 text-sm bg-white border border-zinc-200 outline-none rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-100" name="access_token" type="password" placeholder="Access token" autocomplete="off" data-mastodon-token required> | |
| 94 | <input class="px-3 h-9 text-neutral-950 text-sm bg-white border border-zinc-200 outline-none rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-100" name="instance" placeholder="mastodon.social" autocomplete="url" aria-label="Mastodon instance" data-mastodon-instance required> | |
| 95 | <input class="px-3 h-9 text-neutral-950 text-sm bg-white border border-zinc-200 outline-none rounded-lg focus:border-blue-500 focus:ring-2 focus:ring-blue-100" name="access_token" type="password" placeholder="Access token" autocomplete="off" aria-label="Mastodon access token" data-mastodon-token required> | |
| 96 | 96 | <button class="inline-flex items-center justify-center px-4 h-9 font-bold text-sm text-white bg-neutral-950 hover:bg-neutral-800 rounded-lg disabled:opacity-50 transition-colors" type="submit" data-mastodon-connect-submit>Connect</button> |
| 97 | 97 | <p class="sm:col-span-3 m-0 text-xs text-zinc-500">Create a token in your instance's Preferences → Development → New application (needs the <code class="px-1 py-0.5 text-xs bg-zinc-100 rounded">write:statuses</code> + <code class="px-1 py-0.5 text-xs bg-zinc-100 rounded">write:media</code> scopes).</p> |
| 98 | 98 | </form> |
| 99 | 99 | </article> |
| 100 | <article class="flex gap-5 items-center justify-between p-3.5 bg-white border border-zinc-200 rounded-xl" data-channel="twitter"> | |
| 100 | <article class="flex flex-col gap-3 justify-between sm:flex-row sm:gap-5 sm:items-center p-3.5 bg-white border border-zinc-200 rounded-xl" data-channel="twitter"> | |
| 101 | 101 | <div> |
| 102 | 102 | <h2 class="my-0 font-bold text-[15px]">X (Twitter)</h2> |
| 103 | 103 | <p class="my-1.5 text-[13px] text-slate-700" data-twitter-channel-handle>Not connected</p> |
| @@ -126,11 +126,12 @@ | ||
| 126 | 126 | const detail = root.querySelector('[data-bluesky-channel-detail]') |
| 127 | 127 | const status = root.querySelector('[data-bluesky-channel-status]') |
| 128 | 128 | const pill = root.querySelector('[data-bluesky-channel-pill]') |
| 129 | const toast = window.postlineToast?.show | |
| 130 | const blueskyApi = window.postlineBluesky | |
| 131 | ||
| 132 | 129 | const notify = (message, tone = 'neutral', options = {}) => { |
| 133 | if (toast) toast({ message, tone: tone === 'success' ? 'success' : tone === 'error' ? 'error' : 'info', title: options.title }) | |
| 130 | // Resolve the toast helper at call time — it's a deferred module that | |
| 131 | // loads after this inline script parses, so an eager capture is | |
| 132 | // undefined on a full page load (e.g. the OAuth redirect back here). | |
| 133 | const show = window.postlineToast?.show | |
| 134 | if (show) show({ message, tone: tone === 'success' ? 'success' : tone === 'error' ? 'error' : 'info', title: options.title }) | |
| 134 | 135 | else console.log(message) |
| 135 | 136 | } |
| 136 | 137 | |
| @@ -162,7 +163,7 @@ | ||
| 162 | 163 | |
| 163 | 164 | const refreshStatus = async () => { |
| 164 | 165 | try { |
| 165 | const data = await blueskyApi?.fetchStatus() | |
| 166 | const data = await window.postlineBluesky?.fetchStatus() | |
| 166 | 167 | renderStatus(data) |
| 167 | 168 | } |
| 168 | 169 | catch (error) { |
| @@ -658,7 +659,17 @@ | ||
| 658 | 659 | startMastodon() |
| 659 | 660 | } |
| 660 | 661 | |
| 661 | void refreshStatus() | |
| 662 | // Gate the initial status fetch on DOMContentLoaded so the deferred | |
| 663 | // postline-bluesky module has loaded — otherwise a connected account | |
| 664 | // renders "Not connected" on a full page load. (Mirrors the other | |
| 665 | // providers.) | |
| 666 | const startBluesky = () => void refreshStatus() | |
| 667 | if (document.readyState === 'loading') { | |
| 668 | document.addEventListener('DOMContentLoaded', startBluesky, { once: true }) | |
| 669 | } | |
| 670 | else { | |
| 671 | startBluesky() | |
| 672 | } | |
| 662 | 673 | })() |
| 663 | 674 | </script> |
| 664 | 675 | @endsection |