Skip to content

feat: Add guided tour functionality using driver.js#13

Merged
Gianlu1107 merged 6 commits intomainfrom
tutorial-update
Jan 13, 2026
Merged

feat: Add guided tour functionality using driver.js#13
Gianlu1107 merged 6 commits intomainfrom
tutorial-update

Conversation

@Gecko129
Copy link
Copy Markdown
Owner

  • Integrated driver.js for a guided tour experience in the application.
  • Added a new hook useTour to manage tour steps and interactions.
  • Implemented tour steps for key UI elements including tabs, device list, and settings.
  • Updated translations for tour-related texts in multiple languages.
  • Added a button to start the tour in the app header.
  • Styled tour popovers with a custom CSS theme.
  • Included IDs for various elements to facilitate tour navigation.
  • Added new dependencies in package.json for driver.js.

- Integrated driver.js for a guided tour experience in the application.
- Added a new hook `useTour` to manage tour steps and interactions.
- Implemented tour steps for key UI elements including tabs, device list, and settings.
- Updated translations for tour-related texts in multiple languages.
- Added a button to start the tour in the app header.
- Styled tour popovers with a custom CSS theme.
- Included IDs for various elements to facilitate tour navigation.
- Added new dependencies in package.json for driver.js.
Copilot AI review requested due to automatic review settings January 13, 2026 12:56
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request adds a guided tour functionality to the AirShare application using the driver.js library. The tour walks users through key features including file transfer, device management, history viewing, and settings configuration.

Changes:

  • Added driver.js library (v1.4.0) as a new dependency
  • Created a custom React hook (useTour) to manage tour steps and lifecycle
  • Added tour-related translations across 6 languages (English, Chinese, Italian, French, Spanish, German)
  • Modified multiple components to add unique IDs for tour navigation

Reviewed changes

Copilot reviewed 16 out of 17 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
package.json / package-lock.json Added driver.js v1.4.0 dependency and updated various esbuild packages
src/hooks/useTour.ts New hook implementing tour logic with driver.js integration
src/driver.css Custom CSS theme for tour popovers matching app design
src/App.tsx Added tour initialization, help button in header, and IDs to tab elements
src/i18n/*.json Added tour-related translation strings in 6 languages
src/components/DeviceDetection.tsx Added ID for tour navigation
src/components/DeviceList.tsx Added ID for tour navigation
src/components/TransferHistory.tsx Added ID for tour navigation
src/components/Settings.tsx Added IDs for appearance and language settings
src/components/FileTransfer.tsx Added ID to file drop zone
src/components/DynamicSidebar.tsx Added aria-labels to carousel navigation buttons

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

id="start-tour-btn"
onClick={startTour}
className="p-2 rounded-full hover:bg-secondary/80 transition-colors"
title={t('tour.start', 'Start Tour')}
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tour button is missing an aria-label attribute. While it has a title attribute, an aria-label would improve accessibility for screen reader users. Add aria-label to make the button's purpose clear to assistive technologies.

Suggested change
title={t('tour.start', 'Start Tour')}
title={t('tour.start', 'Start Tour')}
aria-label={t('tour.start', 'Start Tour')}

Copilot uses AI. Check for mistakes.
Comment on lines +51 to +181
setTimeout(() => {
driverObj.current?.moveNext();
}, 400);
}
}
},
{
element: '#file-drop-zone',
popover: {
title: t('tour.dropzone.title', 'Send Files'),
description: t('tour.dropzone.description', 'Drag and drop files here to start sharing.'),
side: "right",
align: 'start'
}
},
{
element: '#tab-devices',
popover: {
title: t('tour.devices.title', 'Devices'),
description: t('tour.devices.description', 'See online devices and manage trusted connections.'),
side: "bottom",
align: 'start',
onNextClick: () => {
setActiveTab('devices');
setTimeout(() => {
driverObj.current?.moveNext();
}, 400);
}
}
},
{
element: '#device-list-container',
popover: {
title: t('tour.devicelist.title', 'Device List'),
description: t('tour.devicelist.description', 'All detected devices on your network appear here. Click to send files to them.'),
side: "top",
align: 'center'
}
},
{
element: '#tab-history',
popover: {
title: t('tour.history.title', 'History'),
description: t('tour.history.description', 'View your recent transfers and statistics.'),
side: "bottom",
align: 'start',
onNextClick: () => {
setActiveTab('history');
setTimeout(() => {
driverObj.current?.moveNext();
}, 400);
}
}
},
{
element: '#history-list',
popover: {
title: t('tour.historylist.title', 'Transfer Log'),
description: t('tour.historylist.description', 'Review past transfers, statuses, and manage records.'),
side: "top",
align: 'center'
}
},
{
element: '#tab-settings',
popover: {
title: t('tour.settings.title', 'Settings'),
description: t('tour.settings.description', 'Configure language, theme, and other preferences.'),
side: "bottom",
align: 'start',
onNextClick: () => {
setActiveTab('settings');
setTimeout(() => {
driverObj.current?.moveNext();
}, 400);
}
}
},
{
element: '#appearance-settings',
popover: {
title: t('tour.appearance.title', 'Appearance'),
description: t('tour.appearance.description', 'Customize the look and feel, including Dark Mode.'),
side: "left",
align: 'start'
}
},
{
element: '#start-tour-btn',
popover: {
title: t('tour.help.title', 'Need Help?'),
description: t('tour.help.description', 'Click here to restart this tour anytime.'),
side: "bottom",
align: 'end'
}
}
];

driverObj.current = driver({
showProgress: true,
animate: true,
steps: steps,
nextBtnText: t('common.next', 'Next'),
prevBtnText: t('common.prev', 'Previous'),
doneBtnText: t('common.done', 'Done'),
popoverClass: 'driverjs-theme',
onDestroyed: () => {
localStorage.setItem('tour_completed', 'true');
setActiveTab('transfer');
},
});
}, [t, setActiveTab]);

const startTour = () => {
const el = document.getElementById('tab-transfer');
if(el) el.click();

setTimeout(() => {
if (driverObj.current) {
driverObj.current.drive();
}
}, 100);
};

const checkAndStartTour = () => {
const tourCompleted = localStorage.getItem('tour_completed');
if (!tourCompleted) {
// Small delay to ensure UI is ready
setTimeout(() => {
startTour();
}, 1500);
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The setTimeout delays (400ms, 100ms, and 1500ms) are magic numbers without explanation. Consider extracting these as named constants with descriptive names (e.g., TAB_TRANSITION_DELAY, TOUR_START_DELAY, INITIAL_TOUR_DELAY) to improve code readability and maintainability.

Copilot uses AI. Check for mistakes.
Comment on lines 261 to +287
"privacy.questions": "¿Preguntas? Contáctanos en",
"terms.questions": "¿Preguntas? Contáctanos en"
"terms.questions": "¿Preguntas? Contáctanos en",
"common.next": "Siguiente",
"common.prev": "Anterior",
"common.done": "Hecho",
"tour.welcome.title": "Bienvenido a AirShare",
"tour.welcome.description": "Tu solución segura y local de intercambio de archivos multiplataforma.",
"tour.dropzone.title": "Enviar archivos",
"tour.dropzone.description": "Arrastra y suelta archivos aquí para comenzar a compartir.",
"tour.transfer.title": "Transferir",
"tour.transfer.description": "Envía archivos a otros dispositivos rápidamente.",
"tour.devices.title": "Dispositivos",
"tour.devices.description": "Ver dispositivos en línea y administrar conexiones de confianza.",
"tour.devicelist.title": "Lista de dispositivos",
"tour.devicelist.description": "Todos los dispositivos detectados en su red aparecen aquí.",
"tour.history.title": "Historial",
"tour.history.description": "Ver tus transferencias recientes y estadísticas.",
"tour.historylist.title": "Registro de transferencias",
"tour.historylist.description": "Revise transferencias pasadas y administre registros.",
"tour.settings.title": "Configuración",
"tour.settings.description": "Configura idioma, tema y otras preferencias.",
"tour.appearance.title": "Apariencia",
"tour.appearance.description": "Personaliza la apariencia, incluido el modo oscuro.",
"tour.help.title": "¿Necesitas ayuda?",
"tour.help.description": "Haz clic aquí para reiniciar este recorrido en cualquier momento.",
"tour.exit.confirm": "¿Estás seguro de que quieres salir del recorrido?",
"tour.start": "Iniciar recorrido"
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation in the JSON file. Line 261 uses a tab character while line 262 has no leading whitespace at all, and lines 263-287 use 2 spaces. This should be standardized to match the existing file format (2 spaces) for consistency throughout the file.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

Comment on lines +30 to +162
useEffect(() => {
const steps: DriveStep[] = [
{
element: '#app-header',
popover: {
title: t('tour.welcome.title', 'Welcome to AirShare'),
description: t('tour.welcome.description', 'Your secure, local cross-platform file sharing solution.'),
side: "bottom",
align: 'start',
showButtons: ['next', 'close']
}
},
{
element: '#tab-transfer',
popover: {
title: t('tour.transfer.title', 'Transfer'),
description: t('tour.transfer.description', 'Send files to other devices quickly.'),
side: "bottom",
align: 'start',
onNextClick: () => {
setActiveTab('transfer');
setTimeout(() => {
driverObj.current?.moveNext();
}, 400);
}
}
},
{
element: '#file-drop-zone',
popover: {
title: t('tour.dropzone.title', 'Send Files'),
description: t('tour.dropzone.description', 'Drag and drop files here to start sharing.'),
side: "right",
align: 'start'
}
},
{
element: '#tab-devices',
popover: {
title: t('tour.devices.title', 'Devices'),
description: t('tour.devices.description', 'See online devices and manage trusted connections.'),
side: "bottom",
align: 'start',
onNextClick: () => {
setActiveTab('devices');
setTimeout(() => {
driverObj.current?.moveNext();
}, 400);
}
}
},
{
element: '#device-list-container',
popover: {
title: t('tour.devicelist.title', 'Device List'),
description: t('tour.devicelist.description', 'All detected devices on your network appear here. Click to send files to them.'),
side: "top",
align: 'center'
}
},
{
element: '#tab-history',
popover: {
title: t('tour.history.title', 'History'),
description: t('tour.history.description', 'View your recent transfers and statistics.'),
side: "bottom",
align: 'start',
onNextClick: () => {
setActiveTab('history');
setTimeout(() => {
driverObj.current?.moveNext();
}, 400);
}
}
},
{
element: '#history-list',
popover: {
title: t('tour.historylist.title', 'Transfer Log'),
description: t('tour.historylist.description', 'Review past transfers, statuses, and manage records.'),
side: "top",
align: 'center'
}
},
{
element: '#tab-settings',
popover: {
title: t('tour.settings.title', 'Settings'),
description: t('tour.settings.description', 'Configure language, theme, and other preferences.'),
side: "bottom",
align: 'start',
onNextClick: () => {
setActiveTab('settings');
setTimeout(() => {
driverObj.current?.moveNext();
}, 400);
}
}
},
{
element: '#appearance-settings',
popover: {
title: t('tour.appearance.title', 'Appearance'),
description: t('tour.appearance.description', 'Customize the look and feel, including Dark Mode.'),
side: "left",
align: 'start'
}
},
{
element: '#start-tour-btn',
popover: {
title: t('tour.help.title', 'Need Help?'),
description: t('tour.help.description', 'Click here to restart this tour anytime.'),
side: "bottom",
align: 'end'
}
}
];

driverObj.current = driver({
showProgress: true,
animate: true,
steps: steps,
nextBtnText: t('common.next', 'Next'),
prevBtnText: t('common.prev', 'Previous'),
doneBtnText: t('common.done', 'Done'),
popoverClass: 'driverjs-theme',
onDestroyed: () => {
localStorage.setItem('tour_completed', 'true');
setActiveTab('transfer');
},
});
}, [t, setActiveTab]);
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The driver object is recreated on every render when translations or setActiveTab change. This is inefficient and could cause issues with tour state. Consider memoizing the driver configuration or using useCallback for the initialization to avoid recreating the driver instance unnecessarily.

Copilot uses AI. Check for mistakes.
localStorage.setItem('tour_completed', 'true');
setActiveTab('transfer');
},
});
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The driver object is not cleaned up when the component unmounts. This could lead to memory leaks if the tour is active when the component is unmounted. Add a cleanup function in the useEffect to call driverObj.current?.destroy() on unmount.

Suggested change
});
});
return () => {
if (driverObj.current) {
driverObj.current.destroy();
driverObj.current = null;
}
};

Copilot uses AI. Check for mistakes.
Comment on lines +165 to +166
const el = document.getElementById('tab-transfer');
if(el) el.click();
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code directly manipulates DOM elements using getElementById and click() which is not aligned with React best practices. Consider using a ref or state management to control tab switching instead of direct DOM manipulation. This approach is fragile and could break if the element ID changes or if the component structure is modified.

Suggested change
const el = document.getElementById('tab-transfer');
if(el) el.click();
// Ensure the transfer tab is active before starting the tour
setActiveTab('transfer');

Copilot uses AI. Check for mistakes.
Comment on lines +22 to +25
* align: 'start' // "start", "center", "end"
* }
* }
*/
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 'align' property is duplicated on line 22. Remove the duplicate line as it serves no purpose and may cause confusion.

Suggested change
* align: 'start' // "start", "center", "end"
* }
* }
*/
* }
* }
*/
*/

Copilot uses AI. Check for mistakes.

useEffect(() => {
checkAndStartTour();
}, []);
Copy link

Copilot AI Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The checkAndStartTour function is called in useEffect without being included in the dependency array. This violates React's exhaustive-deps rule and could lead to stale closures. Either add checkAndStartTour to the dependency array or wrap it with useCallback to ensure it has a stable reference.

Suggested change
}, []);
}, [checkAndStartTour]);

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Copilot AI commented Jan 13, 2026

@Gecko129 I've opened a new pull request, #14, to work on those changes. Once the pull request is ready, I'll request review from you.

Gecko129 and others added 4 commits January 13, 2026 14:08
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Gecko129 <207233579+Gecko129@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Fix inconsistent indentation in es.json translation file
@Gianlu1107 Gianlu1107 merged commit 35d3963 into main Jan 13, 2026
0 of 7 checks passed
@Gianlu1107 Gianlu1107 deleted the tutorial-update branch January 13, 2026 14:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants