PageSourceSearch

https://pressthink.org/j/rosen-archive/frontend/components/WelcomeModal.js?v=3.8.36

js pressthink.org collected 2026-10-02 04:26:26 UTC 3,355 bytes, 101 lines download raw bytes

1import { useEffect, useState } from 'react';
2import { html } from '../html.js?v=3.8.36';
3import { Archive, ArrowRight, X } from 'lucide-react';
4import {
5  readTourState,
6  recordTourOutcome,
7  shouldShowTourEntry,
8  TOUR_OUTCOMES,
9} from '../services/tourState.js?v=3.8.36';
10import { getPrivacyDetailsHref } from '../services/privacyRoute.js?v=3.8.36';
11
12const getStorage = () => {
13  try {
14    return window.localStorage;
15  } catch {
16    return null;
17  }
18};
19
20// Kept under its established component name, but rendered as a non-blocking
21// invitation so a first visit can always use the archive immediately.
22const WelcomeModal = ({ onStart }) => {
23  const [isVisible, setIsVisible] = useState(false);
24
25  useEffect(() => {
26    setIsVisible(shouldShowTourEntry(readTourState(getStorage())));
27  }, []);
28
29  const finishEntry = (outcome) => {
30    recordTourOutcome(getStorage(), outcome);
31    // Always hide for the current session, even when storage is unavailable.
32    setIsVisible(false);
33  };
34
35  const handleDismiss = () => {
36    finishEntry(TOUR_OUTCOMES.dismissed);
37  };
38
39  const handleStart = () => {
40    finishEntry(TOUR_OUTCOMES.completed);
41    onStart?.();
42  };
43
44  const handlePrivacy = () => {
45    finishEntry(TOUR_OUTCOMES.dismissed);
46  };
47
48  if (!isVisible || !onStart) return null;
49
50  return html`
51    <aside
52      className="archive-panel archive-panel--accent archive-welcome-panel fixed z-40 bottom-4 right-4 w-full max-w-[calc(100vw-2rem)] sm:max-w-md"
53      aria-labelledby="welcome-title"
54      aria-describedby="welcome-description"
55    >
56      <div className="archive-welcome-panel__privacy">
57        <span><strong>Privacy:</strong> No tracking cookies; local preferences.</span>
58        <a href=${getPrivacyDetailsHref()} onClick=${handlePrivacy}>Details</a>
59      </div>
60
61      <div className="archive-welcome-panel__body">
62        <button
63          onClick=${handleDismiss}
64          className="archive-action archive-action--quiet archive-welcome-panel__close absolute top-2 right-2 p-3"
65          aria-label="Dismiss Start here invitation"
66        >
67          <${X} className="w-5 h-5" aria-hidden="true" />
68        </button>
69
70        <div className="flex items-center gap-3 pr-8">
71          <div className="bg-stone-900 text-white p-2 flex-shrink-0" aria-hidden="true">
72            <${Archive} className="w-5 h-5" />
73          </div>
74          <h2 id="welcome-title" className="text-xl font-display font-bold text-stone-900">
75            Find your way in
76          </h2>
77        </div>
78        <p id="welcome-description" className="text-sm text-stone-600 leading-relaxed font-body mt-3">
79          Begin with a landmark work, search the collection, or follow an idea across four decades.
80        </p>
81        <div className="flex flex-wrap items-center gap-2 mt-4">
82          <button
83            onClick=${handleStart}
84            className="archive-action archive-action--primary group py-3"
85          >
86            <span>Start here</span>
87            <${ArrowRight} className="w-4 h-4 transition-transform group-hover:translate-x-1" aria-hidden="true" />
88          </button>
89          <button
90            onClick=${handleDismiss}
91            className="archive-action archive-action--quiet py-3"
92          >
93            Maybe later
94          </button>
95        </div>
96      </div>
97    </aside>
98  `;
99};
100
101export default WelcomeModal;

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.