PageSourceSearch

https://boltscribe.ink/assets/views/verify-email.js

js boltscribe.ink collected 2026-10-02 01:57:22 UTC 3,264 bytes, 82 lines download raw bytes

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.