mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-14 10:34:06 +02:00
Add onboarding bypass flag V2 version2 version 2 (#5151)
## Summary - add a shared hook that honors a `bypassOnboarding` query parameter and marks onboarding steps as completed for the session - block onboarding orchestrator and UI elements when the bypass flag is present so tours and popups stay hidden ## Testing - ./gradlew build ------ [Codex Task](https://chatgpt.com/codex/tasks/task_b_693059f866a8832891dd97f3d52ca5a0)
This commit is contained in:
@@ -6,6 +6,7 @@ import { isAuthRoute } from '@app/constants/routes';
|
|||||||
import { dispatchTourState } from '@app/constants/events';
|
import { dispatchTourState } from '@app/constants/events';
|
||||||
import { useOnboardingOrchestrator } from '@app/components/onboarding/orchestrator/useOnboardingOrchestrator';
|
import { useOnboardingOrchestrator } from '@app/components/onboarding/orchestrator/useOnboardingOrchestrator';
|
||||||
import { markStepSeen } from '@app/components/onboarding/orchestrator/onboardingStorage';
|
import { markStepSeen } from '@app/components/onboarding/orchestrator/onboardingStorage';
|
||||||
|
import { useBypassOnboarding } from '@app/components/onboarding/useBypassOnboarding';
|
||||||
import OnboardingTour, { type AdvanceArgs, type CloseArgs } from '@app/components/onboarding/OnboardingTour';
|
import OnboardingTour, { type AdvanceArgs, type CloseArgs } from '@app/components/onboarding/OnboardingTour';
|
||||||
import OnboardingModalSlide from '@app/components/onboarding/OnboardingModalSlide';
|
import OnboardingModalSlide from '@app/components/onboarding/OnboardingModalSlide';
|
||||||
import {
|
import {
|
||||||
@@ -29,6 +30,7 @@ export default function Onboarding() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const bypassOnboarding = useBypassOnboarding();
|
||||||
const { state, actions } = useOnboardingOrchestrator();
|
const { state, actions } = useOnboardingOrchestrator();
|
||||||
const serverExperience = useServerExperience();
|
const serverExperience = useServerExperience();
|
||||||
const onAuthRoute = isAuthRoute(location.pathname);
|
const onAuthRoute = isAuthRoute(location.pathname);
|
||||||
@@ -227,6 +229,10 @@ export default function Onboarding() {
|
|||||||
return modalSlides.findIndex((step) => step.id === currentStep.id);
|
return modalSlides.findIndex((step) => step.id === currentStep.id);
|
||||||
}, [activeFlow, currentStep]);
|
}, [activeFlow, currentStep]);
|
||||||
|
|
||||||
|
if (bypassOnboarding) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (onAuthRoute) {
|
if (onAuthRoute) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
migrateFromLegacyPreferences,
|
migrateFromLegacyPreferences,
|
||||||
} from '@app/components/onboarding/orchestrator/onboardingStorage';
|
} from '@app/components/onboarding/orchestrator/onboardingStorage';
|
||||||
import { accountService } from '@app/services/accountService';
|
import { accountService } from '@app/services/accountService';
|
||||||
|
import { useBypassOnboarding } from '@app/components/onboarding/useBypassOnboarding';
|
||||||
|
|
||||||
const AUTH_ROUTES = ['/login', '/signup', '/auth', '/invite'];
|
const AUTH_ROUTES = ['/login', '/signup', '/auth', '/invite'];
|
||||||
const SESSION_TOUR_REQUESTED = 'onboarding::session::tour-requested';
|
const SESSION_TOUR_REQUESTED = 'onboarding::session::tour-requested';
|
||||||
@@ -142,6 +143,7 @@ export function useOnboardingOrchestrator(
|
|||||||
const serverExperience = useServerExperience();
|
const serverExperience = useServerExperience();
|
||||||
const { config, loading: configLoading } = useAppConfig();
|
const { config, loading: configLoading } = useAppConfig();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
const bypassOnboarding = useBypassOnboarding();
|
||||||
|
|
||||||
const [runtimeState, setRuntimeState] = useState<OnboardingRuntimeState>(() =>
|
const [runtimeState, setRuntimeState] = useState<OnboardingRuntimeState>(() =>
|
||||||
getInitialRuntimeState(defaultState)
|
getInitialRuntimeState(defaultState)
|
||||||
@@ -213,7 +215,8 @@ export function useOnboardingOrchestrator(
|
|||||||
const isOnAuthRoute = AUTH_ROUTES.some((route) => location.pathname.startsWith(route));
|
const isOnAuthRoute = AUTH_ROUTES.some((route) => location.pathname.startsWith(route));
|
||||||
const loginEnabled = config?.enableLogin === true;
|
const loginEnabled = config?.enableLogin === true;
|
||||||
const isUnauthenticatedWithLoginEnabled = loginEnabled && !hasAuthToken();
|
const isUnauthenticatedWithLoginEnabled = loginEnabled && !hasAuthToken();
|
||||||
const shouldBlockOnboarding = isOnAuthRoute || configLoading || isUnauthenticatedWithLoginEnabled;
|
const shouldBlockOnboarding =
|
||||||
|
bypassOnboarding || isOnAuthRoute || configLoading || isUnauthenticatedWithLoginEnabled;
|
||||||
|
|
||||||
const conditionContext = useMemo<OnboardingConditionContext>(() => ({
|
const conditionContext = useMemo<OnboardingConditionContext>(() => ({
|
||||||
...serverExperience,
|
...serverExperience,
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import { ONBOARDING_STEPS } from '@app/components/onboarding/orchestrator/onboardingConfig';
|
||||||
|
import { markStepSeen } from '@app/components/onboarding/orchestrator/onboardingStorage';
|
||||||
|
|
||||||
|
const SESSION_KEY = 'onboarding::bypass-all';
|
||||||
|
const PARAM_KEY = 'bypassOnboarding';
|
||||||
|
|
||||||
|
function isTruthy(value: string | null): boolean {
|
||||||
|
return value?.toLowerCase() === 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
function readStoredBypass(): boolean {
|
||||||
|
if (typeof window === 'undefined') return false;
|
||||||
|
try {
|
||||||
|
return sessionStorage.getItem(SESSION_KEY) === 'true';
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStoredBypass(enabled: boolean): void {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
try {
|
||||||
|
if (enabled) {
|
||||||
|
sessionStorage.setItem(SESSION_KEY, 'true');
|
||||||
|
} else {
|
||||||
|
sessionStorage.removeItem(SESSION_KEY);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Ignore storage errors to avoid blocking the bypass flow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects the `bypassOnboarding` query parameter and stores it in session storage
|
||||||
|
* so that onboarding remains disabled while the app is open. Also marks all steps
|
||||||
|
* as seen to ensure any dependent UI elements remain hidden.
|
||||||
|
*/
|
||||||
|
export function useBypassOnboarding(): boolean {
|
||||||
|
const location = useLocation();
|
||||||
|
const [bypassOnboarding, setBypassOnboarding] = useState<boolean>(() => readStoredBypass());
|
||||||
|
const stepsMarkedRef = useRef(false);
|
||||||
|
|
||||||
|
const shouldBypassFromSearch = useMemo(() => {
|
||||||
|
try {
|
||||||
|
const params = new URLSearchParams(location.search);
|
||||||
|
return isTruthy(params.get(PARAM_KEY));
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}, [location.search]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fromStorage = readStoredBypass();
|
||||||
|
const nextBypass = shouldBypassFromSearch || fromStorage;
|
||||||
|
setBypassOnboarding(nextBypass);
|
||||||
|
if (nextBypass) {
|
||||||
|
setStoredBypass(true);
|
||||||
|
}
|
||||||
|
}, [shouldBypassFromSearch]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!bypassOnboarding || stepsMarkedRef.current) return;
|
||||||
|
stepsMarkedRef.current = true;
|
||||||
|
ONBOARDING_STEPS.forEach((step) => markStepSeen(step.id));
|
||||||
|
}, [bypassOnboarding]);
|
||||||
|
|
||||||
|
return bypassOnboarding;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user