From 0d3e032990baf9e6c6d8b08ce74dd2bcc1982e4d Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Fri, 7 Mar 2025 10:17:06 +0000 Subject: [PATCH 01/21] chore: add intro.js and intro.js-react dependencies --- package.json | 2 ++ pnpm-lock.yaml | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/package.json b/package.json index 2dd74fd99..22708e05a 100644 --- a/package.json +++ b/package.json @@ -120,6 +120,8 @@ "i18next-fs-backend": "^2.3.2", "i18next-http-backend": "^2.5.2", "ini": "^3.0.1", + "intro.js": "^7.2.0", + "intro.js-react": "^1.0.0", "jest-environment-jsdom": "^29.7.0", "jsdom": "^20.0.0", "json5": "^2.2.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 672ba2bb4..7058467a3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -185,6 +185,12 @@ importers: ini: specifier: ^3.0.1 version: 3.0.1 + intro.js: + specifier: ^7.2.0 + version: 7.2.0 + intro.js-react: + specifier: ^1.0.0 + version: 1.0.0(intro.js@7.2.0)(react@18.3.1) jest-environment-jsdom: specifier: ^29.7.0 version: 29.7.0(bufferutil@4.0.8)(utf-8-validate@5.0.10) @@ -5854,6 +5860,15 @@ packages: resolution: {integrity: sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==} engines: {node: '>=10'} + intro.js-react@1.0.0: + resolution: {integrity: sha512-zR8pbTyX20RnCZpJMc0nuHBpsjcr1wFkj3ZookV6Ly4eE/LGpFTQwPsaA61Cryzwiy/tTFsusf4hPU9NpI9UOg==} + peerDependencies: + intro.js: '>=2.5.0' + react: '>=0.14.0' + + intro.js@7.2.0: + resolution: {integrity: sha512-qbMfaB70rOXVBceIWNYnYTpVTiZsvQh/MIkfdQbpA9di9VBfj1GigUPfcCv3aOfsbrtPcri8vTLTA4FcEDcHSQ==} + invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} @@ -16929,6 +16944,13 @@ snapshots: from2: 2.3.0 p-is-promise: 3.0.0 + intro.js-react@1.0.0(intro.js@7.2.0)(react@18.3.1): + dependencies: + intro.js: 7.2.0 + react: 18.3.1 + + intro.js@7.2.0: {} + invariant@2.2.4: dependencies: loose-envify: 1.4.0 From a9f21bdef5d72fa3d7d7e36ffb568f55382cf3d9 Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Fri, 7 Mar 2025 11:09:59 +0000 Subject: [PATCH 02/21] feat: implement guided tour feature for the library section --- public/locales/en/tour.json | 20 +++++ .../components/TourGuide/TourContext.tsx | 77 +++++++++++++++++ .../components/TourGuide/TourGuide.scss | 83 ++++++++++++++++++ .../components/TourGuide/TourGuide.tsx | 76 +++++++++++++++++ .../components/TourGuide/TourSteps.ts | 85 +++++++++++++++++++ .../TourGuide/TourTriggerButton.tsx | 42 +++++++++ src/frontend/components/TourGuide/index.tsx | 5 ++ 7 files changed, 388 insertions(+) create mode 100644 public/locales/en/tour.json create mode 100644 src/frontend/components/TourGuide/TourContext.tsx create mode 100644 src/frontend/components/TourGuide/TourGuide.scss create mode 100644 src/frontend/components/TourGuide/TourGuide.tsx create mode 100644 src/frontend/components/TourGuide/TourSteps.ts create mode 100644 src/frontend/components/TourGuide/TourTriggerButton.tsx create mode 100644 src/frontend/components/TourGuide/index.tsx diff --git a/public/locales/en/tour.json b/public/locales/en/tour.json new file mode 100644 index 000000000..fc85cdef6 --- /dev/null +++ b/public/locales/en/tour.json @@ -0,0 +1,20 @@ +{ + "tour": { + "done": "Done", + "help": "Help", + "library": { + "add_game": "You can add custom games to your library by clicking this button.", + "back_to_top": "Once you have many games, use this button to quickly scroll back to the top.", + "filters": "Use these filters to sort your games and find what you're looking for quickly.", + "finish": "That's it! You now know how to navigate your Library. Happy gaming!", + "games_list": "This is your games collection. Click on any game to install, launch, or modify it.", + "header": "This is the Library header. You can see how many games you have in your collection.", + "refresh": "Click here to refresh your game library to see the latest additions or updates.", + "search": "Search for specific games by title using the search bar.", + "welcome": "Welcome to your Library! This is where all your games are displayed. Let's explore the main features." + }, + "next": "Next", + "prev": "Prev", + "start_tour": "Start guided tour" + } +} diff --git a/src/frontend/components/TourGuide/TourContext.tsx b/src/frontend/components/TourGuide/TourContext.tsx new file mode 100644 index 000000000..ca8e1acbd --- /dev/null +++ b/src/frontend/components/TourGuide/TourContext.tsx @@ -0,0 +1,77 @@ +import React, { createContext, useState, useContext, ReactNode } from 'react' + +type TourState = { + isTourActive: boolean + currentTour: string | null + completedTours: string[] + activateLibraryTour: () => void + deactivateTour: () => void + markTourAsComplete: (tourId: string) => void + isTourCompleted: (tourId: string) => boolean +} + +const initialState: TourState = { + isTourActive: false, + currentTour: null, + completedTours: [], + activateLibraryTour: () => {}, + deactivateTour: () => {}, + markTourAsComplete: () => {}, + isTourCompleted: () => false +} + +const TourContext = createContext(initialState) + +interface TourProviderProps { + children: ReactNode +} + +export const TourProvider: React.FC = ({ children }) => { + const [isTourActive, setIsTourActive] = useState(false) + const [currentTour, setCurrentTour] = useState(null) + const [completedTours, setCompletedTours] = useState(() => { + // Load completed tours from localStorage if available + const saved = localStorage.getItem('hp-completed-tours') + return saved ? JSON.parse(saved) : [] + }) + + const activateLibraryTour = () => { + setCurrentTour('library') + setIsTourActive(true) + } + + const deactivateTour = () => { + setIsTourActive(false) + setCurrentTour(null) + } + + const markTourAsComplete = (tourId: string) => { + if (!completedTours.includes(tourId)) { + const updatedTours = [...completedTours, tourId] + setCompletedTours(updatedTours) + localStorage.setItem('hp-completed-tours', JSON.stringify(updatedTours)) + } + } + + const isTourCompleted = (tourId: string): boolean => { + return completedTours.includes(tourId) + } + + return ( + + {children} + + ) +} + +export const useTourGuide = (): TourState => useContext(TourContext) diff --git a/src/frontend/components/TourGuide/TourGuide.scss b/src/frontend/components/TourGuide/TourGuide.scss new file mode 100644 index 000000000..8a8d780de --- /dev/null +++ b/src/frontend/components/TourGuide/TourGuide.scss @@ -0,0 +1,83 @@ +// Custom styling for intro.js to match HyperPlay design system +.introjs-tooltip { + background-color: var(--background-secondary); + color: var(--text-default); + border-radius: var(--space-xs); + box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.25); + border: 1px solid var(--accent); + min-width: 400px; +} + +.introjs-tooltiptext { + font-size: var(--text-xl); + line-height: 1.5; + padding: var(--space-sm); +} + +.introjs-button { + background-color: var(--background-lighter); + color: var(--text-default); + border-radius: var(--space-3xs-fixed); + font-size: var(--text-xs); + padding: var(--space-3xs) var(--space-xs); + border: 1px solid var(--accent); + text-shadow: none; + + &:hover { + background-color: var(--primary); + color: var(--text-title); + } +} + +.introjs-prevbutton { + margin-right: var(--space-3xs); +} + +.introjs-bullets ul li a { + background-color: var(--background-lighter); + + &.active { + background-color: var(--primary); + } +} + +.introjs-progress { + background-color: var(--background-lighter); + + .introjs-progressbar { + background-color: var(--primary); + } +} + +.introjs-arrow { + &.top, + &.top-right, + &.top-middle { + border-bottom-color: var(--background-secondary); + } + + &.bottom, + &.bottom-right, + &.bottom-middle { + border-top-color: var(--background-secondary); + } + + &.left { + border-right-color: var(--background-secondary); + } + + &.right { + border-left-color: var(--background-secondary); + } +} + +.introjs-helperLayer { + background-color: rgba(255, 255, 255, 0.1); + border: 2px solid var(--primary); + box-shadow: 0 2px 15px rgba(0, 0, 0, 0.4); +} + +.introjs-overlay { + opacity: 0.7 !important; + background-color: var(--background-darker); +} diff --git a/src/frontend/components/TourGuide/TourGuide.tsx b/src/frontend/components/TourGuide/TourGuide.tsx new file mode 100644 index 000000000..7baa8a83b --- /dev/null +++ b/src/frontend/components/TourGuide/TourGuide.tsx @@ -0,0 +1,76 @@ +import React, { useState, useEffect } from 'react' +import { Steps } from 'intro.js-react' +import 'intro.js/introjs.css' +import { useTranslation } from 'react-i18next' +import { useTourGuide } from './TourContext' +import { libraryTourSteps, TourStep } from './TourSteps' +import './TourGuide.scss' + +export const TourGuide: React.FC = () => { + const { t } = useTranslation('tour') + const { isTourActive, currentTour, deactivateTour, markTourAsComplete } = + useTourGuide() + const [stepsEnabled, setStepsEnabled] = useState(false) + const [currentSteps, setCurrentSteps] = useState([]) + const [initialStep, setInitialStep] = useState(0) + + useEffect(() => { + if (isTourActive && currentTour) { + let steps: TourStep[] = [] + + switch (currentTour) { + case 'library': + steps = libraryTourSteps(t) + break + // Add more tour types here as needed + default: + steps = [] + } + + setCurrentSteps(steps) + setInitialStep(0) + setStepsEnabled(true) + } else { + setStepsEnabled(false) + } + }, [isTourActive, currentTour, t]) + + const onExit = () => { + setStepsEnabled(false) + deactivateTour() + } + + const onComplete = () => { + if (currentTour) { + markTourAsComplete(currentTour) + } + deactivateTour() + } + + const options = { + showStepNumbers: false, + showBullets: true, + showProgress: true, + hideNext: false, + hidePrev: false, + exitOnOverlayClick: false, + nextLabel: t('tour.next', 'Next'), + prevLabel: t('tour.prev', 'Prev'), + doneLabel: t('tour.done', 'Done'), + overlayOpacity: 0.7, + scrollToElement: true + } + + return ( + + ) +} + +export default TourGuide diff --git a/src/frontend/components/TourGuide/TourSteps.ts b/src/frontend/components/TourGuide/TourSteps.ts new file mode 100644 index 000000000..f630e4769 --- /dev/null +++ b/src/frontend/components/TourGuide/TourSteps.ts @@ -0,0 +1,85 @@ +import { TFunction } from 'i18next' + +export interface TourStep { + element: string + intro: string + position?: 'top' | 'bottom' | 'left' | 'right' | 'center' + tooltipClass?: string + highlightClass?: string +} + +// Library tour steps +export const libraryTourSteps = (t: TFunction<'tour'>): TourStep[] => [ + { + element: '.Library', + intro: t( + 'tour.library.welcome', + "Welcome to your Library! This is where all your games are displayed. Let's explore the main features." + ), + position: 'center' + }, + { + element: '[data-tour="library-top-header"]', + intro: t( + 'tour.library.header', + 'This is the Library header. You can see how many games you have in your collection.' + ), + position: 'bottom' + }, + { + element: '[data-tour="refresh"]', + intro: t( + 'tour.library.refresh', + 'Click here to refresh your game library to see the latest additions or updates.' + ), + position: 'bottom' + }, + { + element: '[data-tour="add-game"]', + intro: t( + 'tour.library.add_game', + 'You can add custom games to your library by clicking this button.' + ), + position: 'bottom' + }, + { + element: '[data-tour="filters"]', + intro: t( + 'tour.library.filters', + "Use these filters to sort your games and find what you're looking for quickly." + ), + position: 'bottom' + }, + { + element: '[data-testid="searchBar"]', + intro: t( + 'tour.library.search', + 'Search for specific games by title using the search bar.' + ), + position: 'bottom' + }, + { + element: '.Library', + intro: t( + 'tour.library.games_list', + 'This is your games collection. Click on any game to install, launch, or modify it.' + ), + position: 'center' + }, + { + element: '#backToTopBtn', + intro: t( + 'tour.library.back_to_top', + 'Once you have many games, use this button to quickly scroll back to the top.' + ), + position: 'left' + }, + { + element: '.Library', + intro: t( + 'tour.library.finish', + "That's it! You now know how to navigate your Library. Happy gaming!" + ), + position: 'center' + } +] diff --git a/src/frontend/components/TourGuide/TourTriggerButton.tsx b/src/frontend/components/TourGuide/TourTriggerButton.tsx new file mode 100644 index 000000000..cfbe211e3 --- /dev/null +++ b/src/frontend/components/TourGuide/TourTriggerButton.tsx @@ -0,0 +1,42 @@ +import React from 'react' +import { useTranslation } from 'react-i18next' +import { Button } from '@hyperplay/ui' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons' +import { useTourGuide } from './TourContext' + +interface TourTriggerButtonProps { + tourId?: string // If not provided, uses the 'library' tour by default + className?: string +} + +export const TourTriggerButton: React.FC = ({ + tourId = 'library', + className = '' +}) => { + const { t } = useTranslation('tour') + const { activateLibraryTour } = useTourGuide() + + const handleClick = () => { + if (tourId === 'library') { + activateLibraryTour() + } + // Add additional tour activation functions as needed + } + + return ( + + ) +} + +export default TourTriggerButton diff --git a/src/frontend/components/TourGuide/index.tsx b/src/frontend/components/TourGuide/index.tsx new file mode 100644 index 000000000..078748b33 --- /dev/null +++ b/src/frontend/components/TourGuide/index.tsx @@ -0,0 +1,5 @@ +import { TourProvider, useTourGuide } from './TourContext' +import TourGuide from './TourGuide' +import TourTriggerButton from './TourTriggerButton' + +export { TourProvider, useTourGuide, TourGuide, TourTriggerButton } From bc16b42b60b8ba79415b928e6ca36ae2292a3b82 Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Fri, 7 Mar 2025 11:10:34 +0000 Subject: [PATCH 03/21] feat: add tour components to App.tsx and fix stylings --- src/frontend/App.tsx | 198 +++++++++--------- .../Library/components/GamesList/index.tsx | 1 + .../components/LibraryTopBar/index.tsx | 1 + .../screens/Library/index.module.scss | 5 + src/frontend/screens/Library/index.tsx | 21 +- 5 files changed, 130 insertions(+), 96 deletions(-) diff --git a/src/frontend/App.tsx b/src/frontend/App.tsx index aca462d79..a15051526 100644 --- a/src/frontend/App.tsx +++ b/src/frontend/App.tsx @@ -41,6 +41,8 @@ import { QuestsPage } from './screens/Quests' import { NavigateListener } from './NavigateListener' import G7Webview from './screens/G7Webview' import CardPrivacyPolicy from './screens/Onboarding/analytics/CardPrivacyPolicy' +// Import the TourProvider +import { TourProvider } from './components/TourGuide/TourContext' function App() { const { sidebarCollapsed, isSettingsModalOpen, connectivity } = @@ -50,106 +52,112 @@ function App() { return (
- - - - -
- - - - - - - - - - - {isSettingsModalOpen.gameInfo && ( - - )} - - } - /> - } /> - - - - } - > - } /> + {/* Wrap the entire app with the TourProvider */} + + + + + +
+ + + + + + + + + + + {isSettingsModalOpen.gameInfo && ( + + )} + } + path="/" + element={} /> - - } /> - } - /> - } /> - } /> - } /> - } /> - } - /> - } /> - }> - } /> - - - - } /> + } /> + + + + } + > + } /> + } + /> + + } /> + } + /> + } /> + } /> + } /> + } /> + } + /> + } /> + }> + } /> + + + + } /> + + + } + /> + + } /> - - } /> - - } /> - - - - - } /> + + + + } /> + - - } /> - }> - } /> - - -
-
- -
-
- - {onboardingStore.isOnboardingOpen && ( - { - if (disableReason === 'skipped') { - window.api.trackEvent({ event: 'Onboarding Skipped' }) - } - onboardingStore.closeOnboarding() - }} - /> - )} -
- - - - + } /> + }> + } /> + +
+
+
+ +
+
+ + {onboardingStore.isOnboardingOpen && ( + { + if (disableReason === 'skipped') { + window.api.trackEvent({ event: 'Onboarding Skipped' }) + } + onboardingStore.closeOnboarding() + }} + /> + )} +
+ + + + +
) } diff --git a/src/frontend/screens/Library/components/GamesList/index.tsx b/src/frontend/screens/Library/components/GamesList/index.tsx index 00f07b4de..a3d7df25e 100644 --- a/src/frontend/screens/Library/components/GamesList/index.tsx +++ b/src/frontend/screens/Library/components/GamesList/index.tsx @@ -83,6 +83,7 @@ const GamesList = observer( gameListLayout: layout === 'list', firstLane: isFirstLane })} + data-tour="games-list" > {layout === 'list' && (
diff --git a/src/frontend/screens/Library/components/LibraryTopBar/index.tsx b/src/frontend/screens/Library/components/LibraryTopBar/index.tsx index 9e6292e11..d773609f9 100644 --- a/src/frontend/screens/Library/components/LibraryTopBar/index.tsx +++ b/src/frontend/screens/Library/components/LibraryTopBar/index.tsx @@ -49,6 +49,7 @@ export const LibraryTopBar = observer( libraryState.category = val as Category } }} + data-tour="filters" defaultValue={category} classNames={getTabsClassNames( { list: styles.tabsList }, diff --git a/src/frontend/screens/Library/index.module.scss b/src/frontend/screens/Library/index.module.scss index 4866ab186..a13ce4f0f 100644 --- a/src/frontend/screens/Library/index.module.scss +++ b/src/frontend/screens/Library/index.module.scss @@ -70,3 +70,8 @@ justify-content: center; align-items: center; } + +.tourButton { + padding: var(--space-xs); + margin-left: var(--space-md-fixed); +} diff --git a/src/frontend/screens/Library/index.tsx b/src/frontend/screens/Library/index.tsx index e25f58201..0ed461d16 100644 --- a/src/frontend/screens/Library/index.tsx +++ b/src/frontend/screens/Library/index.tsx @@ -26,6 +26,9 @@ import libraryState from '../../state/libraryState' import { observer } from 'mobx-react-lite' import storeAuthState from 'frontend/state/storeAuthState' import SteamInstallButton from './components/SteamInstall/SteamInstallButton' +import { useTourGuide } from 'frontend/components/TourGuide/TourContext' +import TourGuide from 'frontend/components/TourGuide/TourGuide' +import TourTriggerButton from 'frontend/components/TourGuide/TourTriggerButton' const storage = window.localStorage @@ -40,6 +43,7 @@ export default observer(function Library(): JSX.Element { const { layout, epic, gog, platform, connectivity } = useContext(ContextProvider) const { t } = useTranslation() + const { isTourCompleted, activateLibraryTour } = useTourGuide() const isOffline = connectivity.status !== 'online' @@ -97,6 +101,16 @@ export default observer(function Library(): JSX.Element { useEffect(() => { window.api.trackScreen('Library') libraryState.selectedFilter = filters[0] + + // Start library tour if it hasn't been completed + if (!isTourCompleted('library')) { + // Delay to allow components to fully render + const timer = setTimeout(() => { + activateLibraryTour() + }, 1000) + return () => clearTimeout(timer) + } + return }, []) const backToTop = () => { @@ -226,7 +240,7 @@ export default observer(function Library(): JSX.Element { <>
-
+

{t('library.label', 'Library')}

{numberOfGames} @@ -234,6 +248,7 @@ export default observer(function Library(): JSX.Element { {isMac ? : null} +
{showRecentGames && ( )}
+ ) }) From 175af9616474de63106f186abb979359fb2a4d41 Mon Sep 17 00:00:00 2001 From: Flavio F Lima Date: Fri, 7 Mar 2025 11:40:58 +0000 Subject: [PATCH 04/21] feat: add sidebar tour functionality and update tour steps --- public/locales/en/tour.json | 11 +++ .../components/TourGuide/TourContext.tsx | 8 ++ .../components/TourGuide/TourGuide.tsx | 5 +- .../components/TourGuide/TourSteps.ts | 84 +++++++++++++++++++ .../TourGuide/TourTriggerButton.tsx | 34 ++++++-- .../Sidebar/components/SidebarLinks/index.tsx | 34 +++++--- .../components/UI/Sidebar/index.module.scss | 34 ++++++++ src/frontend/components/UI/Sidebar/index.tsx | 16 +++- 8 files changed, 204 insertions(+), 22 deletions(-) diff --git a/public/locales/en/tour.json b/public/locales/en/tour.json index fc85cdef6..e99556100 100644 --- a/public/locales/en/tour.json +++ b/public/locales/en/tour.json @@ -15,6 +15,17 @@ }, "next": "Next", "prev": "Prev", + "sidebar": { + "discord": "Join our Discord community to get help, share experiences, and stay updated.", + "docs": "Access documentation and guides to help you get the most out of HyperPlay.", + "downloads": "Check the status of your downloads, updates, and installations in the Download Manager.", + "finish": "That's it! Now you know how to navigate through HyperPlay using the sidebar.", + "library": "This is your Library where all your games are displayed. Click here to access your game collection.", + "quests": "Complete quests to earn rewards. Participate in challenges and boost your gaming experience.", + "settings": "Configure your app settings, manage accounts, and customize your HyperPlay experience.", + "store": "This is the Store navigation. Access the HyperPlay Store, Epic Games, and GOG stores to browse and purchase games.", + "x": "Follow us on X (formerly Twitter) for news, updates, and announcements." + }, "start_tour": "Start guided tour" } } diff --git a/src/frontend/components/TourGuide/TourContext.tsx b/src/frontend/components/TourGuide/TourContext.tsx index ca8e1acbd..96a02f013 100644 --- a/src/frontend/components/TourGuide/TourContext.tsx +++ b/src/frontend/components/TourGuide/TourContext.tsx @@ -5,6 +5,7 @@ type TourState = { currentTour: string | null completedTours: string[] activateLibraryTour: () => void + activateSidebarTour: () => void deactivateTour: () => void markTourAsComplete: (tourId: string) => void isTourCompleted: (tourId: string) => boolean @@ -15,6 +16,7 @@ const initialState: TourState = { currentTour: null, completedTours: [], activateLibraryTour: () => {}, + activateSidebarTour: () => {}, deactivateTour: () => {}, markTourAsComplete: () => {}, isTourCompleted: () => false @@ -40,6 +42,11 @@ export const TourProvider: React.FC = ({ children }) => { setIsTourActive(true) } + const activateSidebarTour = () => { + setCurrentTour('sidebar') + setIsTourActive(true) + } + const deactivateTour = () => { setIsTourActive(false) setCurrentTour(null) @@ -64,6 +71,7 @@ export const TourProvider: React.FC = ({ children }) => { currentTour, completedTours, activateLibraryTour, + activateSidebarTour, deactivateTour, markTourAsComplete, isTourCompleted diff --git a/src/frontend/components/TourGuide/TourGuide.tsx b/src/frontend/components/TourGuide/TourGuide.tsx index 7baa8a83b..8059e205e 100644 --- a/src/frontend/components/TourGuide/TourGuide.tsx +++ b/src/frontend/components/TourGuide/TourGuide.tsx @@ -3,7 +3,7 @@ import { Steps } from 'intro.js-react' import 'intro.js/introjs.css' import { useTranslation } from 'react-i18next' import { useTourGuide } from './TourContext' -import { libraryTourSteps, TourStep } from './TourSteps' +import { libraryTourSteps, sidebarTourSteps, TourStep } from './TourSteps' import './TourGuide.scss' export const TourGuide: React.FC = () => { @@ -22,6 +22,9 @@ export const TourGuide: React.FC = () => { case 'library': steps = libraryTourSteps(t) break + case 'sidebar': + steps = sidebarTourSteps(t) + break // Add more tour types here as needed default: steps = [] diff --git a/src/frontend/components/TourGuide/TourSteps.ts b/src/frontend/components/TourGuide/TourSteps.ts index f630e4769..b51378c51 100644 --- a/src/frontend/components/TourGuide/TourSteps.ts +++ b/src/frontend/components/TourGuide/TourSteps.ts @@ -83,3 +83,87 @@ export const libraryTourSteps = (t: TFunction<'tour'>): TourStep[] => [ position: 'center' } ] + +// Sidebar tour steps +export const sidebarTourSteps = (t: TFunction): TourStep[] => [ + { + element: '[data-tour="sidebar-store"]', + intro: t( + 'tour.sidebar.store', + 'This is the Store navigation. Access the HyperPlay Store, Epic Games, and GOG stores to browse and purchase games.' + ), + position: 'right' + }, + { + element: '[data-tour="sidebar-library"]', + intro: t( + 'tour.sidebar.library', + 'This is your Library where all your games are displayed. Click here to access your game collection.' + ), + position: 'right' + }, + /* { + element: '[data-tour="sidebar-achievements"]', + intro: t( + 'tour.sidebar.achievements', + "Track your achievements across games. See what you've unlocked and what's still to conquer." + ), + position: 'right' + }, */ + { + element: '[data-tour="sidebar-quests"]', + intro: t( + 'tour.sidebar.quests', + 'Complete quests to earn rewards. Participate in challenges and boost your gaming experience.' + ), + position: 'right' + }, + { + element: '[data-tour="sidebar-downloads"]', + intro: t( + 'tour.sidebar.downloads', + 'Check the status of your downloads, updates, and installations in the Download Manager.' + ), + position: 'right' + }, + { + element: '[data-tour="sidebar-settings"]', + intro: t( + 'tour.sidebar.settings', + 'Configure your app settings, manage accounts, and customize your HyperPlay experience.' + ), + position: 'right' + }, + { + element: '[data-tour="sidebar-discord"]', + intro: t( + 'tour.sidebar.discord', + 'Join our Discord community to get help, share experiences, and stay updated.' + ), + position: 'right' + }, + { + element: '[data-tour="sidebar-x"]', + intro: t( + 'tour.sidebar.x', + 'Follow us on X (formerly Twitter) for news, updates, and announcements.' + ), + position: 'right' + }, + { + element: '[data-tour="sidebar-docs"]', + intro: t( + 'tour.sidebar.docs', + 'Access documentation and guides to help you get the most out of HyperPlay.' + ), + position: 'right' + }, + { + element: '.SidebarLinks', + intro: t( + 'tour.sidebar.finish', + "That's it! Now you know how to navigate through HyperPlay using the sidebar." + ), + position: 'right' + } +] diff --git a/src/frontend/components/TourGuide/TourTriggerButton.tsx b/src/frontend/components/TourGuide/TourTriggerButton.tsx index cfbe211e3..86b8d1c72 100644 --- a/src/frontend/components/TourGuide/TourTriggerButton.tsx +++ b/src/frontend/components/TourGuide/TourTriggerButton.tsx @@ -5,36 +5,52 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons' import { useTourGuide } from './TourContext' +export type TourType = 'library' | 'sidebar' + interface TourTriggerButtonProps { - tourId?: string // If not provided, uses the 'library' tour by default + tourId?: TourType className?: string + buttonType?: 'primary' | 'secondary' | 'tertiary' + showIcon?: boolean + showText?: boolean } export const TourTriggerButton: React.FC = ({ tourId = 'library', - className = '' + className = '', + buttonType = 'tertiary', + showIcon = true, + showText = true }) => { const { t } = useTranslation('tour') - const { activateLibraryTour } = useTourGuide() + const { activateLibraryTour, activateSidebarTour } = useTourGuide() const handleClick = () => { - if (tourId === 'library') { - activateLibraryTour() + switch (tourId) { + case 'library': + activateLibraryTour() + break + case 'sidebar': + activateSidebarTour() + break + default: + activateLibraryTour() } - // Add additional tour activation functions as needed } return ( ) } diff --git a/src/frontend/components/UI/Sidebar/components/SidebarLinks/index.tsx b/src/frontend/components/UI/Sidebar/components/SidebarLinks/index.tsx index bf331ee00..ff7378191 100644 --- a/src/frontend/components/UI/Sidebar/components/SidebarLinks/index.tsx +++ b/src/frontend/components/UI/Sidebar/components/SidebarLinks/index.tsx @@ -76,7 +76,7 @@ export default observer(function SidebarLinks() { return ( <>
-
+
@@ -98,7 +98,7 @@ export default observer(function SidebarLinks() {
-
+
@@ -118,7 +118,10 @@ export default observer(function SidebarLinks() {
{SHOW_ACHIEVEMENTS && ( -
+
)} {SHOW_QUESTS ? ( -
+
) : null} -
+
-
+
-
+
handleExternalLink(window.api.openDiscordLink)} + data-tour="sidebar-discord" >
@@ -208,8 +221,9 @@ export default observer(function SidebarLinks() {
handleExternalLink(window.api.openTwitterLink)} + data-tour="sidebar-x" >
@@ -219,7 +233,7 @@ export default observer(function SidebarLinks() {
-
+
-
+
{(isFullscreen || activeController) && }
diff --git a/src/frontend/components/UI/Sidebar/index.module.scss b/src/frontend/components/UI/Sidebar/index.module.scss index 89e952d2e..91f2ee4d5 100644 --- a/src/frontend/components/UI/Sidebar/index.module.scss +++ b/src/frontend/components/UI/Sidebar/index.module.scss @@ -25,3 +25,37 @@ .Sidebar::-webkit-scrollbar { width: 0px; } + +.sidebarContent { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + justify-content: space-between; +} + +.sidebarTourButtonWrapper { + display: flex; + justify-content: center; + margin-top: auto; + padding: var(--space-sm) 0; +} + +.sidebarTourButton { + margin-top: var(--space-md); + background-color: var(--transparent); + color: var(--text-default); + font-weight: 500; + text-align: center; + transition: background-color 0.2s, color 0.2s; + + svg { + width: 24px; + height: 24px; + } + + &:hover { + background-color: var(--primary); + color: var(--text-title); + } +} diff --git a/src/frontend/components/UI/Sidebar/index.tsx b/src/frontend/components/UI/Sidebar/index.tsx index 47c5bcf54..30f899308 100644 --- a/src/frontend/components/UI/Sidebar/index.tsx +++ b/src/frontend/components/UI/Sidebar/index.tsx @@ -1,12 +1,24 @@ import React from 'react' import SidebarLinks from './components/SidebarLinks' +import TourTriggerButton from 'frontend/components/TourGuide/TourTriggerButton' import './index.scss' import styles from './index.module.scss' const Sidebar = () => { return ( -