Improve the demo feature of the project

This commit is contained in:
Urtzi Alfaro
2025-10-12 18:47:33 +02:00
parent dbc7f2fa0d
commit 7556a00db7
168 changed files with 10102 additions and 18869 deletions

View File

@@ -0,0 +1,209 @@
# Demo Onboarding Tour
Interactive onboarding tour system for BakeryIA demo sessions using Driver.js.
## Quick Start
```typescript
import { useDemoTour } from '@/features/demo-onboarding';
function MyComponent() {
const { startTour, resumeTour, tourState } = useDemoTour();
return (
<button onClick={() => startTour()}>
Start Tour
</button>
);
}
```
## Features
-**12-step desktop tour** in Spanish
-**8-step mobile tour** optimized for small screens
-**State persistence** with auto-resume
-**Analytics tracking** (Google Analytics, Plausible)
-**Conversion CTAs** throughout experience
-**Responsive design** across all devices
-**Accessibility** (ARIA, keyboard navigation)
## Project Structure
```
demo-onboarding/
├── index.ts # Public API exports
├── types.ts # TypeScript interfaces
├── styles.css # Driver.js custom theme
├── config/ # Configuration
│ ├── driver-config.ts # Driver.js setup
│ └── tour-steps.ts # Tour step definitions
├── hooks/ # React hooks
│ └── useDemoTour.ts # Main tour hook
└── utils/ # Utilities
├── tour-state.ts # State management (sessionStorage)
└── tour-analytics.ts # Analytics tracking
```
## API Reference
### `useDemoTour()`
Main hook for controlling the tour.
**Returns:**
```typescript
{
startTour: (fromStep?: number) => void;
resumeTour: () => void;
resetTour: () => void;
tourActive: boolean;
tourState: TourState | null;
}
```
### `getTourState()`
Get current tour state from sessionStorage.
**Returns:** `TourState | null`
### `saveTourState(state: Partial<TourState>)`
Save tour state to sessionStorage.
### `clearTourState()`
Clear tour state from sessionStorage.
### `shouldStartTour()`
Check if tour should auto-start.
**Returns:** `boolean`
### `trackTourEvent(event: TourAnalyticsEvent)`
Track tour analytics event.
## Tour Steps
### Desktop (12 steps)
1. Welcome to Demo Session
2. Real-time Metrics Dashboard
3. Intelligent Alerts
4. Procurement Plans
5. Production Management
6. Database Navigation (Sidebar)
7. Daily Operations (Sidebar)
8. Analytics & AI (Sidebar)
9. Multi-Bakery Selector (Header)
10. Demo Limitations
11. Final CTA
### Mobile (8 steps)
Optimized version with navigation-heavy steps removed.
## State Management
Tour state is stored in `sessionStorage`:
```typescript
interface TourState {
currentStep: number;
completed: boolean;
dismissed: boolean;
lastUpdated: number;
tourVersion: string;
}
```
## Analytics Events
Tracked events:
- `tour_started`
- `tour_step_completed`
- `tour_dismissed`
- `tour_completed`
- `conversion_cta_clicked`
Events are sent to Google Analytics and Plausible (if available).
## Styling
The tour uses a custom theme that matches BakeryIA's design system:
- CSS variables for colors
- Smooth animations
- Dark mode support
- Responsive breakpoints
## Data Attributes
The tour targets elements with `data-tour` attributes:
```tsx
<div data-tour="dashboard-stats">
{/* Tour will highlight this element */}
</div>
```
**Available tour targets:**
- `demo-banner` - Demo banner
- `demo-banner-actions` - Banner action buttons
- `dashboard-stats` - Metrics grid
- `real-time-alerts` - Alerts section
- `procurement-plans` - Procurement section
- `production-plans` - Production section
- `sidebar-database` - Database navigation
- `sidebar-operations` - Operations navigation
- `sidebar-analytics` - Analytics navigation
- `sidebar-menu-toggle` - Mobile menu button
- `header-tenant-selector` - Tenant switcher
## Integration
### Auto-start on Demo Login
```typescript
// DemoPage.tsx
import { markTourAsStartPending } from '@/features/demo-onboarding';
// After creating demo session
markTourAsStartPending();
navigate('/app/dashboard');
```
### Dashboard Auto-start
```typescript
// DashboardPage.tsx
import { useDemoTour, shouldStartTour } from '@/features/demo-onboarding';
const { startTour } = useDemoTour();
const isDemoMode = localStorage.getItem('demo_mode') === 'true';
useEffect(() => {
if (isDemoMode && shouldStartTour()) {
setTimeout(() => startTour(), 1500);
}
}, [isDemoMode, startTour]);
```
## Browser Support
- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+
- Mobile browsers
## Performance
- **Bundle Size**: +5KB gzipped (Driver.js)
- **Runtime**: Negligible
- **Load Time**: No impact (lazy loaded)
## See Also
- [DEMO_ONBOARDING_TOUR.md](../../../../../DEMO_ONBOARDING_TOUR.md) - Full implementation guide
- [Driver.js Documentation](https://driverjs.com/)

View File

@@ -0,0 +1,41 @@
import { Config } from 'driver.js';
export const getDriverConfig = (
onNext?: (stepIndex: number) => void
): Config => ({
showProgress: true,
animate: true,
smoothScroll: true,
allowClose: true,
overlayClickNext: false,
stagePadding: 10,
stageRadius: 8,
allowKeyboardControl: true,
disableActiveInteraction: false,
doneBtnText: 'Crear Cuenta Gratis',
closeBtnText: '×',
nextBtnText: 'Siguiente →',
prevBtnText: '← Anterior',
progressText: 'Paso {{current}} de {{total}}',
popoverClass: 'bakery-tour-popover',
popoverOffset: 10,
onHighlightStarted: (element, step, options) => {
const currentIndex = options.state?.activeIndex || 0;
console.log('[Driver] Highlighting element:', element);
console.log('[Driver] Step:', step);
console.log('[Driver] Current index:', currentIndex);
// Track step when it's highlighted
if (onNext && currentIndex > 0) {
onNext(currentIndex);
}
},
onHighlighted: (element, step, options) => {
console.log('[Driver] Element highlighted successfully:', element);
},
});

View File

@@ -0,0 +1,176 @@
import { DriveStep } from 'driver.js';
export const getDemoTourSteps = (): DriveStep[] => [
{
element: '[data-tour="demo-banner"]',
popover: {
title: '¡Bienvenido a BakeryIA Demo!',
description: 'Estás en una sesión demo de 30 minutos con datos reales de una panadería española. Te guiaremos por las funciones principales de la plataforma. Puedes cerrar el tour en cualquier momento con ESC.',
side: 'bottom',
align: 'center',
},
},
{
element: '[data-tour="dashboard-stats"]',
popover: {
title: 'Métricas en Tiempo Real',
description: 'Aquí ves las métricas clave de tu panadería actualizadas al instante: ventas del día, pedidos pendientes, productos vendidos y alertas de stock crítico.',
side: 'bottom',
align: 'start',
},
},
{
element: '[data-tour="real-time-alerts"]',
popover: {
title: 'Alertas Inteligentes',
description: 'El sistema te avisa automáticamente de stock bajo, pedidos urgentes, predicciones de demanda y oportunidades de producción. Toda la información importante en un solo lugar.',
side: 'top',
align: 'start',
},
},
{
element: '[data-tour="procurement-plans"]',
popover: {
title: 'Planes de Aprovisionamiento',
description: 'Visualiza qué ingredientes necesitas comprar hoy según tus planes de producción. El sistema calcula automáticamente las cantidades necesarias.',
side: 'top',
align: 'start',
},
},
{
element: '[data-tour="production-plans"]',
popover: {
title: 'Gestión de Producción',
description: 'Consulta y gestiona tus órdenes de producción programadas. Puedes ver el estado de cada orden, los ingredientes necesarios y el tiempo estimado.',
side: 'top',
align: 'start',
},
},
{
element: '[data-tour="sidebar-database"]',
popover: {
title: 'Base de Datos de tu Panadería',
description: 'Accede a toda la información de tu negocio: inventario de ingredientes, recetas, proveedores, equipos y equipo de trabajo.',
side: 'right',
align: 'start',
},
},
{
element: '[data-tour="sidebar-operations"]',
popover: {
title: 'Operaciones Diarias',
description: 'Gestiona las operaciones del día a día: aprovisionamiento de ingredientes, producción de recetas y punto de venta (POS) para registrar ventas.',
side: 'right',
align: 'start',
},
},
{
element: '[data-tour="sidebar-analytics"]',
popover: {
title: 'Análisis e Inteligencia Artificial',
description: 'Accede a análisis avanzados de ventas, producción y pronósticos de demanda con IA. Simula escenarios y obtén insights inteligentes para tu negocio.',
side: 'right',
align: 'start',
},
},
{
element: '[data-tour="header-tenant-selector"]',
popover: {
title: 'Multi-Panadería',
description: 'Si gestionas varias panaderías o puntos de venta, puedes cambiar entre ellas fácilmente desde aquí. Cada panadería tiene sus propios datos aislados.',
side: 'bottom',
align: 'end',
},
},
{
element: '[data-tour="demo-banner-actions"]',
popover: {
title: 'Limitaciones del Demo',
description: 'En modo demo puedes explorar todas las funciones, pero algunas acciones destructivas están deshabilitadas. Los cambios que hagas no se guardarán después de que expire la sesión.',
side: 'bottom',
align: 'center',
},
},
{
popover: {
title: '¿Listo para gestionar tu panadería real?',
description: 'Has explorado las funcionalidades principales de BakeryIA. Crea una cuenta gratuita para acceder a todas las funciones sin límites, guardar tus datos de forma permanente y conectar tu negocio real.',
side: 'top',
align: 'center',
},
},
];
export const getMobileTourSteps = (): DriveStep[] => [
{
element: '[data-tour="demo-banner"]',
popover: {
title: '¡Bienvenido a BakeryIA!',
description: 'Sesión demo de 30 minutos con datos reales. Te mostraremos las funciones clave.',
side: 'bottom',
align: 'center',
},
},
{
element: '[data-tour="dashboard-stats"]',
popover: {
title: 'Métricas en Tiempo Real',
description: 'Ventas, pedidos, productos y alertas actualizadas al instante.',
side: 'bottom',
align: 'start',
},
},
{
element: '[data-tour="real-time-alerts"]',
popover: {
title: 'Alertas Inteligentes',
description: 'Stock bajo, pedidos urgentes y predicciones de demanda en un solo lugar.',
side: 'top',
align: 'start',
},
},
{
element: '[data-tour="procurement-plans"]',
popover: {
title: 'Aprovisionamiento',
description: 'Ingredientes que necesitas comprar hoy calculados automáticamente.',
side: 'top',
align: 'start',
},
},
{
element: '[data-tour="production-plans"]',
popover: {
title: 'Producción',
description: 'Gestiona órdenes de producción y consulta ingredientes necesarios.',
side: 'top',
align: 'start',
},
},
{
element: '[data-tour="sidebar-menu-toggle"]',
popover: {
title: 'Menú de Navegación',
description: 'Toca aquí para acceder a Base de Datos, Operaciones y Análisis.',
side: 'bottom',
align: 'start',
},
},
{
element: '[data-tour="demo-banner-actions"]',
popover: {
title: 'Limitaciones del Demo',
description: 'Puedes explorar todo, pero los cambios no se guardan permanentemente.',
side: 'bottom',
align: 'center',
},
},
{
popover: {
title: '¿Listo para tu panadería real?',
description: 'Crea una cuenta gratuita para acceso completo sin límites y datos permanentes.',
side: 'top',
align: 'center',
},
},
];

View File

@@ -0,0 +1,170 @@
import { useState, useCallback, useEffect } from 'react';
import { driver, Driver } from 'driver.js';
import { useNavigate } from 'react-router-dom';
import { getDriverConfig } from '../config/driver-config';
import { getDemoTourSteps, getMobileTourSteps } from '../config/tour-steps';
import { getTourState, saveTourState, clearTourState, clearTourStartPending } from '../utils/tour-state';
import { trackTourEvent } from '../utils/tour-analytics';
import '../styles.css';
export const useDemoTour = () => {
const navigate = useNavigate();
const [tourActive, setTourActive] = useState(false);
const [driverInstance, setDriverInstance] = useState<Driver | null>(null);
const isMobile = window.innerWidth < 768;
const handleTourDestroy = useCallback(() => {
const state = getTourState();
const currentStep = driverInstance?.getActiveIndex() || 0;
if (state && !state.completed) {
saveTourState({
currentStep,
dismissed: true,
});
trackTourEvent({
event: 'tour_dismissed',
step: currentStep,
timestamp: Date.now(),
});
}
setTourActive(false);
clearTourStartPending();
}, [driverInstance]);
const handleStepComplete = useCallback((stepIndex: number) => {
saveTourState({
currentStep: stepIndex + 1,
});
trackTourEvent({
event: 'tour_step_completed',
step: stepIndex,
timestamp: Date.now(),
});
}, []);
const handleTourComplete = useCallback(() => {
saveTourState({
completed: true,
currentStep: 0,
});
trackTourEvent({
event: 'tour_completed',
timestamp: Date.now(),
});
setTourActive(false);
clearTourStartPending();
setTimeout(() => {
trackTourEvent({
event: 'conversion_cta_clicked',
timestamp: Date.now(),
});
navigate('/register?from=demo_tour');
}, 500);
}, [navigate]);
const startTour = useCallback((fromStep: number = 0) => {
console.log('[useDemoTour] startTour called with fromStep:', fromStep);
const steps = isMobile ? getMobileTourSteps() : getDemoTourSteps();
console.log('[useDemoTour] Using', isMobile ? 'mobile' : 'desktop', 'steps, total:', steps.length);
// Check if first element exists
const firstElement = steps[0]?.element;
if (firstElement) {
const el = document.querySelector(firstElement);
console.log('[useDemoTour] First element exists:', !!el, 'selector:', firstElement);
if (!el) {
console.warn('[useDemoTour] First tour element not found in DOM! Delaying tour start...');
// Retry after DOM is ready
setTimeout(() => startTour(fromStep), 500);
return;
}
}
const config = getDriverConfig(handleStepComplete);
const driverObj = driver({
...config,
onDestroyed: (element, step, options) => {
const activeIndex = options.state?.activeIndex || 0;
const isLastStep = activeIndex === steps.length - 1;
console.log('[useDemoTour] Tour destroyed, activeIndex:', activeIndex, 'isLastStep:', isLastStep);
if (isLastStep) {
handleTourComplete();
} else {
handleTourDestroy();
}
},
});
driverObj.setSteps(steps);
setDriverInstance(driverObj);
console.log('[useDemoTour] Driver instance created, starting tour...');
if (fromStep > 0 && fromStep < steps.length) {
driverObj.drive(fromStep);
} else {
driverObj.drive();
}
setTourActive(true);
trackTourEvent({
event: 'tour_started',
timestamp: Date.now(),
});
saveTourState({
currentStep: fromStep,
completed: false,
dismissed: false,
});
clearTourStartPending();
}, [isMobile, handleTourDestroy, handleStepComplete, handleTourComplete]);
const resumeTour = useCallback(() => {
const state = getTourState();
if (state && state.currentStep > 0) {
startTour(state.currentStep);
} else {
startTour();
}
}, [startTour]);
const resetTour = useCallback(() => {
clearTourState();
if (driverInstance) {
driverInstance.destroy();
setDriverInstance(null);
}
setTourActive(false);
}, [driverInstance]);
useEffect(() => {
return () => {
if (driverInstance) {
driverInstance.destroy();
}
};
}, [driverInstance]);
return {
startTour,
resumeTour,
resetTour,
tourActive,
tourState: getTourState(),
};
};

View File

@@ -0,0 +1,4 @@
export { useDemoTour } from './hooks/useDemoTour';
export { getTourState, saveTourState, clearTourState, shouldStartTour, markTourAsStartPending, clearTourStartPending } from './utils/tour-state';
export { trackTourEvent } from './utils/tour-analytics';
export type { TourState, TourStep, TourAnalyticsEvent } from './types';

View File

@@ -0,0 +1,179 @@
/* Import Driver.js base styles */
@import 'driver.js/dist/driver.css';
/* Custom theme for BakeryIA tour */
.driver-popover.bakery-tour-popover {
background: var(--bg-primary);
color: var(--text-primary);
border: 1px solid var(--border-default);
box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
border-radius: 12px;
max-width: 400px;
}
.driver-popover.bakery-tour-popover .driver-popover-title {
font-size: 1.125rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 0.75rem;
line-height: 1.4;
}
.driver-popover.bakery-tour-popover .driver-popover-description {
font-size: 0.9375rem;
color: var(--text-secondary);
line-height: 1.6;
margin-bottom: 1rem;
}
.driver-popover.bakery-tour-popover .driver-popover-progress-text {
font-size: 0.875rem;
color: var(--text-tertiary);
font-weight: 500;
}
.driver-popover.bakery-tour-popover .driver-popover-footer {
display: flex;
align-items: center;
gap: 0.75rem;
margin-top: 1.25rem;
}
.driver-popover.bakery-tour-popover .driver-popover-btn {
padding: 0.625rem 1.25rem;
border-radius: 8px;
font-weight: 600;
font-size: 0.9375rem;
transition: all 0.2s ease;
border: none;
cursor: pointer;
}
.driver-popover.bakery-tour-popover .driver-popover-next-btn {
background: var(--color-primary);
color: white;
flex: 1;
}
.driver-popover.bakery-tour-popover .driver-popover-next-btn:hover {
background: var(--color-primary-dark);
transform: translateY(-1px);
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
}
.driver-popover.bakery-tour-popover .driver-popover-prev-btn {
background: var(--bg-secondary);
color: var(--text-primary);
border: 1px solid var(--border-default);
}
.driver-popover.bakery-tour-popover .driver-popover-prev-btn:hover {
background: var(--bg-tertiary);
border-color: var(--border-hover);
}
.driver-popover.bakery-tour-popover .driver-popover-close-btn {
position: absolute;
top: 1rem;
right: 1rem;
width: 2rem;
height: 2rem;
border-radius: 6px;
background: var(--bg-secondary);
color: var(--text-secondary);
font-size: 1.5rem;
line-height: 1;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
border: none;
padding: 0;
}
.driver-popover.bakery-tour-popover .driver-popover-close-btn:hover {
background: var(--bg-tertiary);
color: var(--text-primary);
}
.driver-popover.bakery-tour-popover .driver-popover-arrow-side-top.driver-popover-arrow {
border-top-color: var(--bg-primary);
}
.driver-popover.bakery-tour-popover .driver-popover-arrow-side-bottom.driver-popover-arrow {
border-bottom-color: var(--bg-primary);
}
.driver-popover.bakery-tour-popover .driver-popover-arrow-side-left.driver-popover-arrow {
border-left-color: var(--bg-primary);
}
.driver-popover.bakery-tour-popover .driver-popover-arrow-side-right.driver-popover-arrow {
border-right-color: var(--bg-primary);
}
/*
* Driver.js Overlay Styling
* Driver.js v1.3.6 uses SVG with a cutout path for the spotlight effect
* DO NOT override position, width, height, or other layout properties
* Only customize visual appearance
*/
/* SVG Overlay - only customize the fill color */
.driver-overlay svg {
/* The SVG path fill color for the dark overlay */
fill: rgba(0, 0, 0, 0.75);
}
/* Prevent backdrop-filter from interfering */
.driver-overlay {
backdrop-filter: none !important;
}
/* Visual emphasis for highlighted element - adds outline */
.driver-active-element {
outline: 4px solid var(--color-primary) !important;
outline-offset: 4px !important;
}
/* Prevent theme glass effects from interfering */
.driver-overlay.glass-effect,
.driver-popover.glass-effect,
.driver-active-element.glass-effect {
backdrop-filter: none !important;
}
/* Mobile responsive */
@media (max-width: 640px) {
.driver-popover.bakery-tour-popover {
max-width: calc(100vw - 2rem);
margin: 0 1rem;
}
.driver-popover.bakery-tour-popover .driver-popover-title {
font-size: 1rem;
}
.driver-popover.bakery-tour-popover .driver-popover-description {
font-size: 0.875rem;
}
.driver-popover.bakery-tour-popover .driver-popover-btn {
padding: 0.5rem 1rem;
font-size: 0.875rem;
}
}
/* Last step special styling (CTA) */
.driver-popover.bakery-tour-popover:has(.driver-popover-next-btn:contains("Crear Cuenta")) .driver-popover-next-btn {
background: linear-gradient(135deg, var(--color-primary) 0%, #d97706 100%);
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.2);
font-weight: 700;
padding: 0.75rem 1.5rem;
}
.driver-popover.bakery-tour-popover:has(.driver-popover-next-btn:contains("Crear Cuenta")) .driver-popover-next-btn:hover {
transform: translateY(-2px);
box-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.3);
}

View File

@@ -0,0 +1,26 @@
export interface TourState {
currentStep: number;
completed: boolean;
dismissed: boolean;
lastUpdated: number;
tourVersion: string;
}
export interface TourStep {
element?: string;
popover: {
title: string;
description: string;
side?: 'top' | 'right' | 'bottom' | 'left';
align?: 'start' | 'center' | 'end';
};
onNext?: () => void;
onPrevious?: () => void;
}
export interface TourAnalyticsEvent {
event: 'tour_started' | 'tour_step_completed' | 'tour_completed' | 'tour_dismissed' | 'conversion_cta_clicked';
step?: number;
timestamp: number;
sessionId?: string;
}

View File

@@ -0,0 +1,40 @@
import { TourAnalyticsEvent } from '../types';
export const trackTourEvent = (event: TourAnalyticsEvent): void => {
try {
const demoSessionId = localStorage.getItem('demo_session_id');
const enrichedEvent = {
...event,
sessionId: demoSessionId || undefined,
};
console.log('[Tour Analytics]', enrichedEvent);
if (window.gtag) {
window.gtag('event', event.event, {
event_category: 'demo_tour',
event_label: event.step !== undefined ? `step_${event.step}` : undefined,
session_id: demoSessionId,
});
}
if (window.plausible) {
window.plausible(event.event, {
props: {
step: event.step,
session_id: demoSessionId,
},
});
}
} catch (error) {
console.error('Error tracking tour event:', error);
}
};
declare global {
interface Window {
gtag?: (...args: any[]) => void;
plausible?: (event: string, options?: { props?: Record<string, any> }) => void;
}
}

View File

@@ -0,0 +1,84 @@
import { TourState } from '../types';
const TOUR_STATE_KEY = 'bakery_demo_tour_state';
const TOUR_VERSION = '1.0.0';
export const getTourState = (): TourState | null => {
try {
const stored = sessionStorage.getItem(TOUR_STATE_KEY);
if (!stored) return null;
const state = JSON.parse(stored) as TourState;
if (state.tourVersion !== TOUR_VERSION) {
clearTourState();
return null;
}
return state;
} catch (error) {
console.error('Error reading tour state:', error);
return null;
}
};
export const saveTourState = (state: Partial<TourState>): void => {
try {
const currentState = getTourState() || {
currentStep: 0,
completed: false,
dismissed: false,
lastUpdated: Date.now(),
tourVersion: TOUR_VERSION,
};
const newState: TourState = {
...currentState,
...state,
lastUpdated: Date.now(),
tourVersion: TOUR_VERSION,
};
sessionStorage.setItem(TOUR_STATE_KEY, JSON.stringify(newState));
} catch (error) {
console.error('Error saving tour state:', error);
}
};
export const clearTourState = (): void => {
try {
sessionStorage.removeItem(TOUR_STATE_KEY);
} catch (error) {
console.error('Error clearing tour state:', error);
}
};
export const shouldStartTour = (): boolean => {
const tourState = getTourState();
const shouldStart = sessionStorage.getItem('demo_tour_should_start') === 'true';
console.log('[shouldStartTour] tourState:', tourState);
console.log('[shouldStartTour] shouldStart flag:', shouldStart);
// If explicitly marked to start, always start (unless already completed)
if (shouldStart) {
if (tourState && tourState.completed) {
console.log('[shouldStartTour] Tour already completed, not starting');
return false;
}
console.log('[shouldStartTour] Should start flag is true, starting tour');
return true;
}
// No explicit start flag, don't auto-start
console.log('[shouldStartTour] No start flag, not starting');
return false;
};
export const markTourAsStartPending = (): void => {
sessionStorage.setItem('demo_tour_should_start', 'true');
};
export const clearTourStartPending = (): void => {
sessionStorage.removeItem('demo_tour_should_start');
};