All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 18m52s
287 lines
7.0 KiB
TypeScript
287 lines
7.0 KiB
TypeScript
import { useEffect, useCallback, useRef } from 'react';
|
|
import { driver, Driver, DriveStep } from 'driver.js';
|
|
import 'driver.js/dist/driver.css';
|
|
|
|
const ONBOARDING_STORAGE_KEY = 'flyer_crawler_onboarding_completed';
|
|
|
|
// Custom CSS to match design system: pastel colors, sharp borders, minimalist
|
|
const DRIVER_CSS = `
|
|
.driver-popover {
|
|
background-color: #f0fdfa !important;
|
|
border: 2px solid #0d9488 !important;
|
|
border-radius: 0 !important;
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06) !important;
|
|
max-width: 320px !important;
|
|
}
|
|
|
|
.driver-popover-title {
|
|
color: #134e4a !important;
|
|
font-size: 1rem !important;
|
|
font-weight: 600 !important;
|
|
margin-bottom: 0.5rem !important;
|
|
}
|
|
|
|
.driver-popover-description {
|
|
color: #1f2937 !important;
|
|
font-size: 0.875rem !important;
|
|
line-height: 1.5 !important;
|
|
}
|
|
|
|
.driver-popover-progress-text {
|
|
color: #0d9488 !important;
|
|
font-size: 0.75rem !important;
|
|
font-weight: 500 !important;
|
|
}
|
|
|
|
.driver-popover-navigation-btns {
|
|
gap: 0.5rem !important;
|
|
}
|
|
|
|
.driver-popover-prev-btn,
|
|
.driver-popover-next-btn {
|
|
background-color: #14b8a6 !important;
|
|
color: white !important;
|
|
border: 1px solid #0d9488 !important;
|
|
border-radius: 0 !important;
|
|
padding: 0.5rem 1rem !important;
|
|
font-size: 0.875rem !important;
|
|
font-weight: 500 !important;
|
|
transition: background-color 0.15s ease !important;
|
|
}
|
|
|
|
.driver-popover-prev-btn:hover,
|
|
.driver-popover-next-btn:hover {
|
|
background-color: #115e59 !important;
|
|
}
|
|
|
|
.driver-popover-prev-btn {
|
|
background-color: #ccfbf1 !important;
|
|
color: #134e4a !important;
|
|
}
|
|
|
|
.driver-popover-prev-btn:hover {
|
|
background-color: #99f6e4 !important;
|
|
}
|
|
|
|
.driver-popover-close-btn {
|
|
color: #0d9488 !important;
|
|
font-size: 1.25rem !important;
|
|
}
|
|
|
|
.driver-popover-close-btn:hover {
|
|
color: #115e59 !important;
|
|
}
|
|
|
|
.driver-popover-arrow-side-left,
|
|
.driver-popover-arrow-side-right,
|
|
.driver-popover-arrow-side-top,
|
|
.driver-popover-arrow-side-bottom {
|
|
border-color: #0d9488 !important;
|
|
}
|
|
|
|
.driver-popover-arrow-side-left::after,
|
|
.driver-popover-arrow-side-right::after,
|
|
.driver-popover-arrow-side-top::after,
|
|
.driver-popover-arrow-side-bottom::after {
|
|
border-color: transparent !important;
|
|
}
|
|
|
|
.driver-popover-arrow-side-left::before {
|
|
border-right-color: #f0fdfa !important;
|
|
}
|
|
|
|
.driver-popover-arrow-side-right::before {
|
|
border-left-color: #f0fdfa !important;
|
|
}
|
|
|
|
.driver-popover-arrow-side-top::before {
|
|
border-bottom-color: #f0fdfa !important;
|
|
}
|
|
|
|
.driver-popover-arrow-side-bottom::before {
|
|
border-top-color: #f0fdfa !important;
|
|
}
|
|
|
|
.driver-overlay {
|
|
background-color: rgba(0, 0, 0, 0.5) !important;
|
|
}
|
|
|
|
.driver-active-element {
|
|
box-shadow: 0 0 0 4px #14b8a6 !important;
|
|
}
|
|
|
|
/* Dark mode support */
|
|
@media (prefers-color-scheme: dark) {
|
|
.driver-popover {
|
|
background-color: #1f2937 !important;
|
|
border-color: #14b8a6 !important;
|
|
}
|
|
|
|
.driver-popover-title {
|
|
color: #ccfbf1 !important;
|
|
}
|
|
|
|
.driver-popover-description {
|
|
color: #e5e7eb !important;
|
|
}
|
|
|
|
.driver-popover-arrow-side-left::before {
|
|
border-right-color: #1f2937 !important;
|
|
}
|
|
|
|
.driver-popover-arrow-side-right::before {
|
|
border-left-color: #1f2937 !important;
|
|
}
|
|
|
|
.driver-popover-arrow-side-top::before {
|
|
border-bottom-color: #1f2937 !important;
|
|
}
|
|
|
|
.driver-popover-arrow-side-bottom::before {
|
|
border-top-color: #1f2937 !important;
|
|
}
|
|
}
|
|
`;
|
|
|
|
const tourSteps: DriveStep[] = [
|
|
{
|
|
element: '[data-tour="flyer-uploader"]',
|
|
popover: {
|
|
title: 'Upload Flyers',
|
|
description:
|
|
'Upload a grocery flyer here by clicking or dragging a PDF/image file. Our AI will extract prices and items automatically.',
|
|
side: 'bottom',
|
|
align: 'start',
|
|
},
|
|
},
|
|
{
|
|
element: '[data-tour="extracted-data-table"]',
|
|
popover: {
|
|
title: 'Extracted Items',
|
|
description:
|
|
'View all extracted items from your flyers here. You can watch items to track price changes and deals.',
|
|
side: 'top',
|
|
align: 'start',
|
|
},
|
|
},
|
|
{
|
|
element: '[data-tour="watch-button"]',
|
|
popover: {
|
|
title: 'Watch Items',
|
|
description:
|
|
'Click the eye icon to watch items and get notified when prices drop or deals appear.',
|
|
side: 'left',
|
|
align: 'start',
|
|
},
|
|
},
|
|
{
|
|
element: '[data-tour="watched-items"]',
|
|
popover: {
|
|
title: 'Watched Items',
|
|
description:
|
|
'Your watched items appear here. Track prices across different stores and get deal alerts.',
|
|
side: 'left',
|
|
align: 'start',
|
|
},
|
|
},
|
|
{
|
|
element: '[data-tour="price-chart"]',
|
|
popover: {
|
|
title: 'Active Deals',
|
|
description:
|
|
'Active deals show here with price comparisons. See which store has the best price!',
|
|
side: 'left',
|
|
align: 'start',
|
|
},
|
|
},
|
|
{
|
|
element: '[data-tour="shopping-list"]',
|
|
popover: {
|
|
title: 'Shopping Lists',
|
|
description:
|
|
'Create shopping lists from your watched items and get the best prices automatically.',
|
|
side: 'left',
|
|
align: 'start',
|
|
},
|
|
},
|
|
];
|
|
|
|
// Inject custom styles into the document head
|
|
const injectStyles = () => {
|
|
const styleId = 'driver-js-custom-styles';
|
|
if (!document.getElementById(styleId)) {
|
|
const style = document.createElement('style');
|
|
style.id = styleId;
|
|
style.textContent = DRIVER_CSS;
|
|
document.head.appendChild(style);
|
|
}
|
|
};
|
|
|
|
export const useOnboardingTour = () => {
|
|
const driverRef = useRef<Driver | null>(null);
|
|
|
|
const markTourComplete = useCallback(() => {
|
|
localStorage.setItem(ONBOARDING_STORAGE_KEY, 'true');
|
|
}, []);
|
|
|
|
const startTour = useCallback(() => {
|
|
injectStyles();
|
|
|
|
if (driverRef.current) {
|
|
driverRef.current.destroy();
|
|
}
|
|
|
|
driverRef.current = driver({
|
|
showProgress: true,
|
|
steps: tourSteps,
|
|
nextBtnText: 'Next',
|
|
prevBtnText: 'Previous',
|
|
doneBtnText: 'Done',
|
|
progressText: 'Step {{current}} of {{total}}',
|
|
onDestroyed: () => {
|
|
markTourComplete();
|
|
},
|
|
});
|
|
|
|
driverRef.current.drive();
|
|
}, [markTourComplete]);
|
|
|
|
const skipTour = useCallback(() => {
|
|
if (driverRef.current) {
|
|
driverRef.current.destroy();
|
|
}
|
|
markTourComplete();
|
|
}, [markTourComplete]);
|
|
|
|
const replayTour = useCallback(() => {
|
|
startTour();
|
|
}, [startTour]);
|
|
|
|
// Auto-start tour on mount if not completed
|
|
useEffect(() => {
|
|
const hasCompletedOnboarding = localStorage.getItem(ONBOARDING_STORAGE_KEY);
|
|
if (!hasCompletedOnboarding) {
|
|
// Small delay to ensure DOM elements are mounted
|
|
const timer = setTimeout(() => {
|
|
startTour();
|
|
}, 500);
|
|
return () => clearTimeout(timer);
|
|
}
|
|
}, [startTour]);
|
|
|
|
// Cleanup on unmount
|
|
useEffect(() => {
|
|
return () => {
|
|
if (driverRef.current) {
|
|
driverRef.current.destroy();
|
|
}
|
|
};
|
|
}, []);
|
|
|
|
return {
|
|
skipTour,
|
|
replayTour,
|
|
startTour,
|
|
};
|
|
};
|