1import { html } from '../../shared/lib/html.js'; 2 3export const title = 'Verify Email - Bolt Scribe'; 4 5// Module-level guard: app.js re-invokes the view function on every renderApp 6// (route/state/auth-change), and the data-bound DOM attribute doesn't survive a 7// full body re-render. Without this, a background re-render would fire a second 8// POST /api/auth/verify with an already-used token â a false "Verification 9// Failed". /auth/verify is only ever reached via an email link (full page 10// load), so a per-page-load flag is the right scope. 11let verifyInitiated = false; 12 13export default (html, state, actions) => { 14 // Client-side: run the verification and render the result directly into the 15 // page. We do NOT rely on a signal changing to re-render the view â this 16 // app only re-renders views on route/state/auth events, so a status signal 17 // flip would never update the DOM (the page would sit on the spinner 18 // forever even after a successful verify). Mirror the login/register pattern: 19 // imperatively render into the article. 20 if (typeof window !== 'undefined') { 21 window.queueMicrotask(async () => { 22 if (verifyInitiated) return; 23 verifyInitiated = true; 24 const { render, html: uhtml } = await import('../lib/uhtml.js'); 25 const host = document.querySelector('[data-page="verify"]'); 26 if (!host) return; 27 28 const token = new URLSearchParams(window.location.search).get('token') || ''; 29 30 const show = (node) => render(host, node); 31 32 if (!token) { 33 show(uhtml` 34 <h1 class="text-error">Invalid Link</h1> 35 <p class="text-muted">This verification link is invalid. Please check your email for the correct link.</p> 36 `); 37 return; 38 } 39 40 try { 41 const response = await fetch('/api/auth/verify', { 42 method: 'POST', 43 headers: { 'Content-Type': 'application/json' }, 44 body: JSON.stringify({ token }), 45 }); 46 47 if (response.ok) { 48 // Full page load to dashboard â the SSR reads the freshly-updated DB 49 // (email_verified=1), so the "verify your email" banner won't show. 50 // SPA navigation would use stale client state (email_verified=0). 51 window.location.href = '/dashboard'; 52 return; 53 } else { 54 const result = await response.json().catch(() => ({})); 55 const msg = result.error?.message || result.error || 'Verification failed'; 56 show(uhtml` 57 <h1 class="text-error">Verification Failed</h1> 58 <p class="text-muted">${msg}</p> 59 <footer><a href="/login">Back to Login</a></footer> 60 `); 61 } 62 } catch (err) { 63 console.error('Email verification error:', err); 64 show(uhtml` 65 <h1 class="text-error">Verification Failed</h1> 66 <p class="text-muted">Network error. Please try again.</p> 67 <footer><a href="/login">Back to Login</a></footer> 68 `); 69 } 70 }); 71 } 72 73 // SSR: render the initial loading state (client replaces it with the result). 74 return html` 75 <main> 76 <article data-page="verify" class="text-center"> 77 <h1>Verifying your email...</h1> 78 <div class="spinner"></div> 79 </article> 80 </main> 81 `; 82};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.