From e20fd595b8bd2322bf54cfabe575189b710d77d0 Mon Sep 17 00:00:00 2001 From: HeavenVR Date: Tue, 20 Oct 2026 21:35:41 +0200 Subject: [PATCH] chore: remove welcome screen and onboarding tour The welcome screen passed its sunset date, and the tour could only be started from it. Removes the welcome screen, the driver.js tour and its data-tour anchors, the PUBLIC_DISABLE_ONBOARDING flag and the driver.js dependency. --- .env | 1 - package.json | 1 - pnpm-lock.yaml | 8 - src/app.css | 51 -- src/env.ts | 6 - src/lib/state/auth-state.svelte.ts | 21 - src/lib/tour/onboarding-state.ts | 78 --- src/lib/tour/welcome-tour.ts | 471 ------------------ .../(app)/settings/connections/+page.svelte | 2 +- src/routes/(app)/shares/user/+layout.svelte | 30 +- src/routes/(app)/shockers/own/+page.svelte | 23 +- src/routes/+layout.server.ts | 28 +- src/routes/+layout.svelte | 11 - src/routes/WelcomeScreen.svelte | 345 ------------- src/worker-configuration.d.ts | 3 +- 15 files changed, 14 insertions(+), 1065 deletions(-) delete mode 100644 src/lib/tour/onboarding-state.ts delete mode 100644 src/lib/tour/welcome-tour.ts delete mode 100644 src/routes/WelcomeScreen.svelte diff --git a/.env b/.env index 5ebaa652..f69e5bcc 100644 --- a/.env +++ b/.env @@ -9,7 +9,6 @@ PUBLIC_TURNSTILE_DEV_BYPASS_VALUE=INVALID PUBLIC_DEVELOPMENT_BANNER=false -PUBLIC_DISABLE_ONBOARDING=false PUBLIC_DISABLE_SHOCKER_MAP=true diff --git a/package.json b/package.json index 62e34f30..c1e4b055 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,6 @@ "@opentelemetry/sdk-logs": "^0.223.0", "@opentelemetry/sdk-trace-node": "^2.12.0", "@opentelemetry/sdk-trace-web": "^2.12.0", - "driver.js": "^1.10.1", "temporal-polyfill": "^1.0.5", "vite": "^8.3.4" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b63160a7..4a445d54 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -207,9 +207,6 @@ importers: '@opentelemetry/sdk-trace-web': specifier: ^2.12.0 version: 2.12.0(@opentelemetry/api@1.9.1) - driver.js: - specifier: ^1.10.1 - version: 1.10.1 temporal-polyfill: specifier: ^1.0.5 version: 1.0.5 @@ -2309,9 +2306,6 @@ packages: resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} engines: {node: '>=12'} - driver.js@1.10.1: - resolution: {integrity: sha512-mpcJ15eIRWnGMykeIWjr0CdQF00/JzImFOEXy/VponYgMbP/y46t24MPBDO3PV/rH6CjdSe3T7WQ9cP3GvEPdQ==} - electron-to-chromium@1.5.440: resolution: {integrity: sha512-SghDzqdJokdz8zP8YNlvS74+CwLRoUPDGvP/gFA+HrKVw+pOshUAgx8pXR37xl/u16zGae746rFNWvZ/22kU2Q==} @@ -5058,8 +5052,6 @@ snapshots: dotenv@17.4.2: {} - driver.js@1.10.1: {} - electron-to-chromium@1.5.440: {} enhanced-resolve@5.25.1: diff --git a/src/app.css b/src/app.css index 78be2016..5e77f2a5 100644 --- a/src/app.css +++ b/src/app.css @@ -1,54 +1,3 @@ @import 'tailwindcss'; @import 'tw-animate-css'; @import '@openshock/svelte-core/theme.css'; - -/* driver.js theme override — match the dark UI */ -.driver-popover { - background: #13131a; - color: rgba(255, 255, 255, 0.85); - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 0.75rem; -} -.driver-popover-title { - color: #fff; - font-weight: 600; -} -.driver-popover-description { - color: rgba(255, 255, 255, 0.7); -} -.driver-popover-arrow-side-left.driver-popover-arrow, -.driver-popover-arrow-side-right.driver-popover-arrow, -.driver-popover-arrow-side-top.driver-popover-arrow, -.driver-popover-arrow-side-bottom.driver-popover-arrow { - border-color: #13131a; -} -.driver-popover-progress-text { - color: rgba(255, 255, 255, 0.5); - font-size: 0.75rem; -} -.driver-popover-footer button { - background: rgba(255, 255, 255, 0.08) !important; - color: #fff !important; - border: 1px solid rgba(255, 255, 255, 0.12); - border-radius: 0.5rem; - padding: 0.375rem 0.875rem; - font-size: 0.875rem; - text-shadow: none !important; -} -.driver-popover-footer button:hover { - background: rgba(255, 255, 255, 0.14) !important; -} -.driver-popover-footer .driver-popover-next-btn { - background: #fff !important; - color: #000 !important; - border-color: transparent; -} -.driver-popover-footer .driver-popover-next-btn:hover { - background: rgba(255, 255, 255, 0.9) !important; -} -.driver-popover-close-btn { - color: rgba(255, 255, 255, 0.5); -} -.driver-popover-close-btn:hover { - color: #fff; -} diff --git a/src/env.ts b/src/env.ts index 3cc0a5d6..37667279 100644 --- a/src/env.ts +++ b/src/env.ts @@ -155,12 +155,6 @@ export const variables = defineEnvVars({ description: 'Shows the "development deployment" banner above the app shell.', schema: isTruthy, }, - PUBLIC_DISABLE_ONBOARDING: { - public: true, - static: true, - description: 'Suppresses the first-run onboarding tour.', - schema: isTruthy, - }, PUBLIC_DISABLE_SHOCKER_MAP: { public: true, static: true, diff --git a/src/lib/state/auth-state.svelte.ts b/src/lib/state/auth-state.svelte.ts index 612a1a97..87950d78 100644 --- a/src/lib/state/auth-state.svelte.ts +++ b/src/lib/state/auth-state.svelte.ts @@ -1,13 +1,5 @@ import { registerOnUnauthorized } from '#lib/errorhandling/apiErrorHandling.js'; import { destroySignalR, initializeSignalR } from '#lib/signalr/user.svelte.js'; -import { - hasCompletedTour, - hasSeenWelcome, - isOnboardingDisabled, - markTourCompleted, - startWelcomeTour, -} from '#lib/tour/welcome-tour.js'; -import { dialog } from '@openshock/svelte-core/components/dialog-manager'; import { userState } from './user-state.svelte'; export const AuthStatus = { @@ -33,18 +25,6 @@ export const authState = { }, }; -async function maybeTourPrompt() { - if (isOnboardingDisabled() || hasCompletedTour() || !hasSeenWelcome()) return; - const result = await dialog.confirm({ - title: 'Take the quick tour?', - desc: "You skipped it earlier. It only takes a minute and shows you what's new.", - confirmButtonText: 'Sure, show me', - cancelButtonText: 'No thanks', - }); - if (result.confirmed) await startWelcomeTour(); - else markTourCompleted(); -} - /** * Wires up the reactive side-effects of auth state: * - SignalR connects/disconnects as the user logs in/out. @@ -61,7 +41,6 @@ export function startAuthLifecycle() { $effect(() => { const self = userState.self; if (self && !prevSelf) { - void maybeTourPrompt(); void initializeSignalR(); } else if (!self) { void destroySignalR(); diff --git a/src/lib/tour/onboarding-state.ts b/src/lib/tour/onboarding-state.ts deleted file mode 100644 index 5c0bd744..00000000 --- a/src/lib/tour/onboarding-state.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { PUBLIC_DISABLE_ONBOARDING } from '$app/env/public'; -import { isTruthy } from '@openshock/svelte-core/utils'; - -export const WELCOME_COOKIE_NAME = 'os.welcomed'; -// Pre-cookie builds stored "welcome seen" under this localStorage key. Kept only -// for the migration fallback in hasSeenWelcome(). -const LEGACY_WELCOME_STORAGE_KEY = 'os.welcomeVersion'; -const WELCOME_COOKIE_EXPIRES = new Date('2026-07-10'); -// Retire the welcome screen one day before the cookie expires, so it can't briefly -// reappear for everyone in the window where the cookie lapses. -const WELCOME_SUNSET = new Date(WELCOME_COOKIE_EXPIRES.getTime() - 24 * 60 * 60 * 1000); - -export function isWelcomeSunset(): boolean { - return Date.now() >= WELCOME_SUNSET.getTime(); -} - -const CURRENT_TOUR_VERSION = 1; -const TOUR_VERSION_KEY = 'os.tourCompletedVersion'; - -export function isOnboardingDisabled(): boolean { - return PUBLIC_DISABLE_ONBOARDING; -} - -function readWelcomeCookie(): boolean { - try { - const escapedName = WELCOME_COOKIE_NAME.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - const match = document.cookie.match(new RegExp(`(?:^|;\\s*)${escapedName}=([^;]*)`)); - return match ? isTruthy(decodeURIComponent(match[1])) : false; - } catch { - return false; - } -} - -export function hasSeenWelcome(): boolean { - // Check cookie (new path) then fall back to localStorage (migration). isTruthy - // also accepts the legacy version value "1", so pre-existing state still counts. - if (readWelcomeCookie()) return true; - try { - return isTruthy(localStorage.getItem(LEGACY_WELCOME_STORAGE_KEY)); - } catch { - return false; - } -} - -export function markWelcomed(): void { - try { - // Synchronous document.cookie write. The async cookieStore API would also work - // (Chrome 87+, Firefox 140+, Safari 18.4+), but for a one-shot write it buys - // nothing and drops older browsers, so the plain string write is preferred. - document.cookie = `${WELCOME_COOKIE_NAME}=true; path=/; expires=${WELCOME_COOKIE_EXPIRES.toUTCString()}; SameSite=Lax`; - } catch { - // ignore - } -} - -export function shouldShowWelcome(): boolean { - if (isOnboardingDisabled() || isWelcomeSunset()) return false; - return !hasSeenWelcome(); -} - -export function hasCompletedTour(): boolean { - if (isOnboardingDisabled()) return true; - try { - const raw = localStorage.getItem(TOUR_VERSION_KEY); - const n = raw ? parseInt(raw, 10) : 0; - return Number.isFinite(n) && n >= CURRENT_TOUR_VERSION; - } catch { - return false; - } -} - -export function markTourCompleted(): void { - try { - localStorage.setItem(TOUR_VERSION_KEY, String(CURRENT_TOUR_VERSION)); - } catch { - // ignore - } -} diff --git a/src/lib/tour/welcome-tour.ts b/src/lib/tour/welcome-tour.ts deleted file mode 100644 index efaf9e88..00000000 --- a/src/lib/tour/welcome-tour.ts +++ /dev/null @@ -1,471 +0,0 @@ -import { browser } from '$app/env'; -import { goto } from '$app/navigation'; -import { resolve as resolvePath } from '$app/paths'; -import { driver, type Driver, type DriveStep } from 'driver.js'; -import 'driver.js/dist/driver.css'; -import { isOnboardingDisabled, markTourCompleted } from './onboarding-state'; - -export { - hasCompletedTour, - hasSeenWelcome, - isOnboardingDisabled, - markTourCompleted, - markWelcomed, - shouldShowWelcome, -} from './onboarding-state'; - -const SIDEBAR_TOGGLE_SEL = 'button[title="Toggle Sidebar"]'; -const SIDEBAR_ROOT_SEL = '[data-slot="sidebar"]'; -const SIDEBAR_LINK = (href: string) => `[data-slot="sidebar-content"] a[href$="${href}"]`; -const USER_MENU_SEL = 'header button:has(> img.rounded-full)'; -const TOUR = (tag: string) => `[data-tour="${tag}"]`; - -type SidebarState = 'desktop-expanded' | 'desktop-collapsed' | 'mobile-open' | 'mobile-closed'; - -function isMobileViewport(): boolean { - return typeof window !== 'undefined' && window.matchMedia('(max-width: 767px)').matches; -} - -function readSidebarState(): SidebarState { - if (isMobileViewport()) { - return document.querySelector('[data-slot="sidebar"][data-mobile="true"]') - ? 'mobile-open' - : 'mobile-closed'; - } - return document.querySelector('[data-slot="sidebar"][data-state="expanded"]') - ? 'desktop-expanded' - : 'desktop-collapsed'; -} - -function waitForElement(selector: string, timeoutMs = 5000): Promise { - const existing = document.querySelector(selector); - if (existing) return Promise.resolve(existing); - return new Promise((resolve) => { - const obs = new MutationObserver(() => { - const el = document.querySelector(selector); - if (el) { - obs.disconnect(); - clearTimeout(timer); - resolve(el); - } - }); - obs.observe(document.body, { childList: true, subtree: true }); - const timer = setTimeout(() => { - obs.disconnect(); - resolve(document.querySelector(selector)); - }, timeoutMs); - }); -} - -function waitForMobileSidebar(): Promise { - return new Promise((resolve) => setTimeout(resolve, 250)); -} - -interface TourStep { - // `action` advances on a real click of `element` (no Next button shown). - // `info` advances on the Next button. - kind: 'info' | 'action'; - // `sidebar` = element lives inside the sidebar drawer (must be open on mobile). - // `page` = element lives in the page content (sidebar should be closed on mobile). - // `any` = no sidebar management needed (default). - mobileContext?: 'sidebar' | 'page' | 'any'; - element?: string; - title: string; - description: string; - side?: DriveStep['popover'] extends infer P ? (P extends { side?: infer S } ? S : never) : never; - // If set, the step is skipped when the predicate returns true at tour-start. - skipIf?: () => boolean; -} - -function sidebarOverviewSteps(): TourStep[] { - return [ - { - kind: 'info', - mobileContext: 'sidebar', - element: SIDEBAR_LINK('/home'), - title: 'Home', - description: 'Your dashboard, with quick access to the shockers you control most often.', - side: 'right', - }, - { - kind: 'info', - mobileContext: 'sidebar', - element: SIDEBAR_LINK('/hubs'), - title: 'Hubs', - description: 'Your hubs live here. Pair new ones and check their status from this page.', - side: 'right', - }, - { - kind: 'info', - mobileContext: 'sidebar', - element: SIDEBAR_LINK('/shares/public'), - title: 'Public shares', - description: 'Generate links anyone can use, with limits and expiry you set.', - side: 'right', - }, - ]; -} - -function shockersDeepDive(): TourStep[] { - return [ - { - kind: 'action', - mobileContext: 'sidebar', - element: SIDEBAR_LINK('/shockers/own'), - title: 'Your shockers', - description: 'This is where your own shockers live. Click to open the page.', - side: 'right', - }, - { - kind: 'info', - mobileContext: 'page', - element: TOUR('shockers-add'), - title: 'Add a shocker', - description: - 'Pair a new shocker to one of your hubs. You can set its RF protocol, name, and limits when you add it.', - side: 'bottom', - }, - { - kind: 'info', - mobileContext: 'page', - element: TOUR('shockers-viewmode'), - title: 'Pick a control layout', - description: - 'Classic, Rich, or Simple. Switch between layouts depending on how you like to trigger shockers.', - side: 'bottom', - }, - { - kind: 'info', - mobileContext: 'page', - element: TOUR('shockers-layout'), - title: 'Group & layout options', - description: 'Group cards by hub and tweak how the grid is organised.', - side: 'bottom', - }, - ]; -} - -function userSharesDeepDive(): TourStep[] { - return [ - { - kind: 'action', - mobileContext: 'sidebar', - element: SIDEBAR_LINK('/shares/user'), - title: 'User shares', - description: 'Permanent shares between you and another OpenShock user. Click to take a look.', - side: 'right', - }, - { - kind: 'info', - mobileContext: 'page', - element: TOUR('user-shares-tabs'), - title: 'Three tabs', - description: - 'The tabs let you navigate between shares you give out, shares you receive, and pending invites.', - side: 'bottom', - }, - { - kind: 'info', - mobileContext: 'page', - element: TOUR('user-shares-tab-outgoing'), - title: 'Shares — outgoing', - description: - 'Everything you have shared with others. Each entry shows which shockers you granted and the permissions you set.', - side: 'bottom', - }, - { - kind: 'info', - mobileContext: 'page', - element: TOUR('user-shares-tab-incoming'), - title: 'Shared with Me', - description: - 'Shockers other users have granted you access to. They appear here once the owner accepts your request or sends you a share link.', - side: 'bottom', - }, - { - kind: 'info', - mobileContext: 'page', - element: TOUR('user-shares-tab-invites'), - title: 'Invites', - description: - 'Pending share requests waiting for the other party to accept. Once accepted they move to the Shares or Shared with Me tab.', - side: 'bottom', - }, - { - kind: 'info', - mobileContext: 'page', - element: TOUR('user-shares-new'), - title: 'Create a share', - description: - 'Pick a user, choose which shockers, and configure exactly what they can do: shock, vibrate, sound, intensity caps, durations.', - side: 'bottom', - }, - { - kind: 'info', - mobileContext: 'page', - element: TOUR('user-shares-redeem'), - title: 'Redeem a code', - description: - "Got a share code from someone else? Paste it here to accept the shockers they've shared with you.", - side: 'bottom', - }, - ]; -} - -function connectionsDeepDive(): TourStep[] { - return [ - { - kind: 'action', - mobileContext: 'sidebar', - element: SIDEBAR_LINK('/settings/connections'), - title: 'Connections', - description: - 'Link third-party accounts so you can sign in faster next time. Click to open it.', - side: 'right', - }, - { - kind: 'info', - mobileContext: 'page', - element: TOUR('connections-link'), - title: 'OAuth connections', - description: - "Link a Discord, GitHub, or other supported account and you'll be able to sign in with one click. No password needed.", - side: 'bottom', - }, - ]; -} - -function desktopSteps(initial: SidebarState): TourStep[] { - const startsExpanded = initial === 'desktop-expanded'; - - const steps: TourStep[] = []; - - if (startsExpanded) { - steps.push({ - kind: 'info', - element: SIDEBAR_ROOT_SEL, - title: 'Your navigation', - description: 'Everything you can reach in the app lives in this sidebar.', - side: 'right', - }); - steps.push(...sidebarOverviewSteps()); - steps.push({ - kind: 'action', - element: SIDEBAR_TOGGLE_SEL, - title: 'Collapse it', - description: 'Click to collapse the sidebar. Even collapsed, every icon is still navigable.', - side: 'bottom', - }); - } else { - steps.push({ - kind: 'info', - element: SIDEBAR_ROOT_SEL, - title: 'The icon rail', - description: 'When collapsed, the sidebar is a compact icon rail.', - side: 'right', - }); - steps.push({ - kind: 'action', - element: SIDEBAR_TOGGLE_SEL, - title: 'Expand the sidebar', - description: 'Click to open it up so we can walk through what each section does.', - side: 'bottom', - }); - steps.push(...sidebarOverviewSteps()); - } - - steps.push(...shockersDeepDive()); - steps.push(...userSharesDeepDive()); - steps.push(...connectionsDeepDive()); - - steps.push({ - kind: 'info', - element: USER_MENU_SEL, - title: 'Your account menu', - description: 'Profile, account settings, and logout are tucked in here.', - side: 'bottom', - skipIf: () => !document.querySelector(USER_MENU_SEL), - }); - - steps.push({ - kind: 'info', - title: "You're all set", - description: - 'Explore at your own pace. If we add a major new feature later, we may show you a fresh tour the next time you visit.', - }); - - return steps; -} - -function mobileSteps(): TourStep[] { - const steps: TourStep[] = []; - - steps.push({ - kind: 'info', - mobileContext: 'sidebar', - element: SIDEBAR_ROOT_SEL, - title: 'Your navigation', - description: 'Everything you can reach in the app lives here.', - side: 'right', - }); - - steps.push(...shockersDeepDive()); - steps.push(...userSharesDeepDive()); - steps.push(...connectionsDeepDive()); - - steps.push({ - kind: 'info', - title: "You're all set", - description: 'Tap any icon in the menu to navigate.', - }); - - return steps; -} - -function toDriverStep(step: TourStep): DriveStep { - return { - element: step.element, - // Highlighted element is only interactive on action steps (where the user - // is explicitly asked to click it). On info steps we don't want a stray - // click on a sidebar link to navigate away from the tour. - disableActiveInteraction: step.kind !== 'action', - popover: { - title: step.title, - description: step.description, - side: step.side ?? 'bottom', - showButtons: step.kind === 'action' ? ['previous', 'close'] : ['next', 'previous', 'close'], - }, - }; -} - -/** - * Starts an interactive welcome tour. Steps adapt to the current sidebar - * state and require the user to click into pages themselves; we wait for - * each next highlight target to render before advancing. - */ -export async function startWelcomeTour(): Promise { - if (!browser || isOnboardingDisabled()) return; - const initial = readSidebarState(); - const allSteps = isMobileViewport() ? mobileSteps() : desktopSteps(initial); - const tourSteps = allSteps.filter((s) => !s.skipIf?.()); - const driverSteps = tourSteps.map(toDriverStep); - - // On mobile, ensure the sidebar is open before the first step. - if (isMobileViewport() && tourSteps[0]?.mobileContext === 'sidebar') { - if (readSidebarState() === 'mobile-closed') { - (document.querySelector(SIDEBAR_TOGGLE_SEL) as HTMLElement)?.click(); - await waitForMobileSidebar(); - } - } - - return new Promise((resolve) => { - const ctx: { d: Driver | null; cleanup: (() => void) | null; completed: boolean } = { - d: null, - cleanup: null, - completed: false, - }; - - const wireUpActionAdvance = async () => { - ctx.cleanup?.(); - ctx.cleanup = null; - - const idx = ctx.d!.getActiveIndex(); - if (idx === undefined) return; - const step = tourSteps[idx]; - - if (step?.kind !== 'action' || !step.element) return; - - const target = document.querySelector(step.element); - if (!target) return; - - const handler = async () => { - ctx.cleanup?.(); - ctx.cleanup = null; - const next = tourSteps[idx + 1]; - // On mobile, close sidebar before showing page content steps. - if (isMobileViewport() && next?.mobileContext === 'page') { - if (readSidebarState() === 'mobile-open') { - (document.querySelector(SIDEBAR_TOGGLE_SEL) as HTMLElement)?.click(); - await waitForMobileSidebar(); - } - } - if (next?.element) { - await waitForElement(next.element); - } else { - await new Promise((r) => setTimeout(r, 80)); - } - ctx.d!.moveNext(); - }; - target.addEventListener('click', handler, { once: true }); - ctx.cleanup = () => target.removeEventListener('click', handler); - }; - - const handleNextClick = async () => { - const idx = ctx.d!.getActiveIndex(); - if (idx === undefined) return ctx.d!.moveNext(); - const next = tourSteps[idx + 1]; - if (!next) ctx.completed = true; // last step — Done button - // On mobile, open sidebar before showing sidebar steps reached via Next. - if (isMobileViewport() && next?.mobileContext === 'sidebar') { - if (readSidebarState() === 'mobile-closed') { - (document.querySelector(SIDEBAR_TOGGLE_SEL) as HTMLElement)?.click(); - await waitForMobileSidebar(); - if (next.element) await waitForElement(next.element); - } - } - ctx.d!.moveNext(); - }; - - const handlePrevClick = async () => { - const idx = ctx.d!.getActiveIndex(); - if (idx === undefined) return ctx.d!.movePrevious(); - const prev = tourSteps[idx - 1]; - // On mobile, open sidebar when going back to a sidebar step. - if (isMobileViewport() && prev?.mobileContext === 'sidebar') { - if (readSidebarState() === 'mobile-closed') { - (document.querySelector(SIDEBAR_TOGGLE_SEL) as HTMLElement)?.click(); - await waitForMobileSidebar(); - if (prev.element) await waitForElement(prev.element); - } - } - // On mobile, close sidebar when going back to a page step. - if (isMobileViewport() && prev?.mobileContext === 'page') { - if (readSidebarState() === 'mobile-open') { - (document.querySelector(SIDEBAR_TOGGLE_SEL) as HTMLElement)?.click(); - await waitForMobileSidebar(); - } - } - ctx.d!.movePrevious(); - }; - - ctx.d = driver({ - showProgress: true, - allowClose: true, - overlayClickBehavior: () => {}, - progressText: '{{current}} of {{total}}', - nextBtnText: 'Next', - prevBtnText: 'Back', - doneBtnText: 'Done', - steps: driverSteps, - onHighlighted: wireUpActionAdvance, - onNextClick: handleNextClick, - onPrevClick: handlePrevClick, - onCloseClick: (_el, _step, { driver: d }) => { - ctx.cleanup?.(); - markTourCompleted(); - d.destroy(); - resolve(); - }, - onDeselected: () => { - ctx.cleanup?.(); - ctx.cleanup = null; - }, - onDestroyed: () => { - ctx.cleanup?.(); - if (ctx.completed) goto(resolvePath('home')); - resolve(); - }, - }); - ctx.d.drive(); - }); -} diff --git a/src/routes/(app)/settings/connections/+page.svelte b/src/routes/(app)/settings/connections/+page.svelte index 7e16545e..f46c98c8 100644 --- a/src/routes/(app)/settings/connections/+page.svelte +++ b/src/routes/(app)/settings/connections/+page.svelte @@ -100,7 +100,7 @@ {#snippet child({ props })} - diff --git a/src/routes/(app)/shares/user/+layout.svelte b/src/routes/(app)/shares/user/+layout.svelte index ce54670f..09f46c35 100644 --- a/src/routes/(app)/shares/user/+layout.svelte +++ b/src/routes/(app)/shares/user/+layout.svelte @@ -80,35 +80,21 @@ - (redeemDialogOpen = true)} + >Redeem Code - +
- + - navigateTo('outgoing')} - data-tour="user-shares-tab-outgoing">Shares - navigateTo('incoming')} - data-tour="user-shares-tab-incoming">Shared with Me - navigateTo('invites')} - data-tour="user-shares-tab-invites">Invites navigateTo('outgoing')}>Shares + navigateTo('incoming')} + >Shared with Me + navigateTo('invites')}>Invites
diff --git a/src/routes/(app)/shockers/own/+page.svelte b/src/routes/(app)/shockers/own/+page.svelte index 400936fe..f1cfe393 100644 --- a/src/routes/(app)/shockers/own/+page.svelte +++ b/src/routes/(app)/shockers/own/+page.svelte @@ -195,12 +195,7 @@ {:else}
- {/snippet} @@ -259,13 +248,7 @@ {#snippet child({ props })} - {/snippet} diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts index 21e3c8b0..d7eec530 100644 --- a/src/routes/+layout.server.ts +++ b/src/routes/+layout.server.ts @@ -1,10 +1,4 @@ -import { - isOnboardingDisabled, - isWelcomeSunset, - WELCOME_COOKIE_NAME, -} from '#lib/tour/onboarding-state.js'; import { SIDEBAR_COOKIE_NAME } from '@openshock/svelte-core/components/ui/sidebar/constants.js'; -import { isTruthy } from '@openshock/svelte-core/utils'; import type { LayoutServerLoad } from './$types'; export const load: LayoutServerLoad = ({ cookies }) => { @@ -13,25 +7,5 @@ export const load: LayoutServerLoad = ({ cookies }) => { // state and we avoid a collapse-then-expand flash on hydration. const sidebarOpen = cookies.get(SIDEBAR_COOKIE_NAME) === 'true'; - // When onboarding is disabled the client unmounts the screen immediately on - // hydration, so SSR-ing it just hurts LCP — skip it server-side too. - if (isOnboardingDisabled()) { - return { sidebarOpen, showWelcome: false }; - } - - // Past the sunset date the welcome screen is retired — never SSR it, and clear - // the cookie if a returning visitor still has it set. - if (isWelcomeSunset()) { - if (cookies.get(WELCOME_COOKIE_NAME) !== undefined) { - cookies.delete(WELCOME_COOKIE_NAME, { path: '/' }); - } - return { sidebarOpen, showWelcome: false }; - } - - // SSR the welcome screen for visitors who haven't seen it, so the logo is in - // the initial HTML rather than loaded after JS runs. Any non-truthy/missing - // cookie (including malformed) counts as "not seen". - const showWelcome = !isTruthy(cookies.get(WELCOME_COOKIE_NAME)); - - return { sidebarOpen, showWelcome }; + return { sidebarOpen }; }; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index e7eb1c65..592b975d 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -14,7 +14,6 @@ import Footer from './Footer.svelte'; import Header from './Header.svelte'; import Sidebar from './Sidebar.svelte'; - import WelcomeScreen from './WelcomeScreen.svelte'; import '../app.css'; import { IsMobile } from '@openshock/svelte-core/hooks'; import { DialogManager } from '@openshock/svelte-core/components/dialog-manager'; @@ -36,8 +35,6 @@ let sidebarOpen = $state(untrack(() => data.sidebarOpen)); const isOpen = $derived(mobile.current ? false : sidebarOpen); - let welcomeOpen = $state(untrack(() => data.showWelcome)); - onMount(() => maybePromptTelemetryConsent()); @@ -50,14 +47,6 @@ - {#if welcomeOpen} - { - welcomeOpen = false; - }} - /> - {/if} - (sidebarOpen = v)}> - - - -
- {#each steps as _, i (i)} - - {/each} -
- -
-
- {#key step} -
- {#if isFirst} -

Welcome to OpenShock

- {:else} -

- Step {step + 1} of {steps.length} -

-

- {steps[step].title} -

- {/if} -
- {@render steps[step].body()} -
-
- {/key} -
- -
-
- -
- - {#if isLast && canStartTour} - - {:else} - - {/if} -
-
-
-
-
- -{#snippet stepWelcome()} -
-

- Welcome to the new -

- - OpenShock - -

- frontend -

- -

- Take the quick tour, or skip ahead and dive in. -

-
-{/snippet} - -{#snippet stepFeatures()} -

- Over 2 years of work. Faster, fully mobile-friendly, - and a much better base for everything we want to build next. -

-
    - {#each [{ t: 'Reworked shocker dashboard', d: 'Groundwork for customizable layouts.' }, { t: 'Web Terminal', d: 'Configure your hub and flash firmware straight from the browser. No installs.' }, { t: 'OAuth sign-in', d: 'Log in with accounts you already have.' }, { t: 'Better sharing', d: 'Public links, user shares, invite tracking, and per-shocker shortcuts.' }] as item, i (item.t)} -
  • -
    {item.t}
    -
    {item.d}
    -
  • - {/each} -
-{/snippet} - -{#snippet stepRedirects()} -

- Old bookmarks like - - openshock.app/#/dashboard/home - - forward automatically. Nothing for you to do. -

-

- The legacy site stays online during the transition. We'll announce a sunset date once the new - site has full feature parity. -

-{/snippet} - -{#snippet stepFeedback()} -

Something broken or missing? The faster you report it, the faster we can fix it.

- - - -

Lots more is landing soon. Thanks for sticking with us.

-{/snippet} diff --git a/src/worker-configuration.d.ts b/src/worker-configuration.d.ts index 3fb9b032..5cf534ff 100644 --- a/src/worker-configuration.d.ts +++ b/src/worker-configuration.d.ts @@ -9,7 +9,6 @@ interface __BaseEnv_Env { PUBLIC_DISCORD_INVITE_URL: string; PUBLIC_TURNSTILE_DEV_BYPASS_VALUE: string; PUBLIC_DEVELOPMENT_BANNER: string; - PUBLIC_DISABLE_ONBOARDING: string; PUBLIC_DISABLE_SHOCKER_MAP: string; PUBLIC_SITE_URL: string; PUBLIC_SITE_SHORT_URL: string; @@ -31,5 +30,5 @@ type StringifyValues> = { [Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string; }; declare namespace NodeJS { - interface ProcessEnv extends StringifyValues> {} + interface ProcessEnv extends StringifyValues> {} }