(
+
+ {currentStep}
+ {' '}
+ OF
+ {maxSteps}
+
+)
+
+StepCounter.propTypes = {
+ currentStep: PropTypes.number.isRequired,
+ maxSteps: PropTypes.number.isRequired
+}
+
+const TourButtons = ({ stepIndex, setStepIndex }) => (
+
+ setStepIndex(stepIndex - 1)}
+ >
+ Previous
+
+ setStepIndex(stepIndex + 1)}
+ >
+ Next
+
+
+)
+
+TourButtons.propTypes = {
+ stepIndex: PropTypes.number.isRequired,
+ setStepIndex: PropTypes.func.isRequired
+}
+
+const SearchTour = ({ runTour, setRunTour }) => {
+ const [stepIndex, setStepIndex] = useState(0)
+ const MAX_STEPS = 12
+
+ useEffect(() => {
+ const dontShowTour = localStorage.getItem('dontShowTour')
+ if (dontShowTour === 'true') {
+ setRunTour(false)
+ }
+ }, [setRunTour])
+
+ useEffect(() => {
+ const handleKeyDown = (event) => {
+ if (event.key === 'ArrowRight') {
+ setStepIndex((prevIndex) => Math.min(prevIndex + 1, MAX_STEPS + 1))
+ } else if (event.key === 'ArrowLeft') {
+ setStepIndex((prevIndex) => Math.max(prevIndex - 1, 0))
+ }
+ }
+
+ window.addEventListener('keydown', handleKeyDown)
+
+ return () => {
+ window.removeEventListener('keydown', handleKeyDown)
+ }
+ }, [])
+
+ useEffect(() => {
+ if (runTour) {
+ setStepIndex(0)
+ }
+
+ localStorage.setItem('dontShowTour', runTour ? 'false' : 'true')
+ }, [runTour])
+
+ useEffect(() => {
+ if (stepIndex === 6) { // Scrolling to the top to ensure "Browse Portals" is visible.
+ const element = document.querySelector('.sidebar__content .simplebar-content-wrapper')
+ if (element) {
+ element.scrollTop = 0
+ }
+ }
+ }, [stepIndex])
+
+ const steps = [
+ {
+ target: '.search',
+ content: (
+
+
Welcome to Earthdata Search!
+
Let’s start with a quick tour...
+
+ Get acquainted with Earthdata Search by taking our guided tour, where you’ll learn
+ how to search for data, use the map, create your first project, and manage your
+ preferences.
+
+
+ If you want to skip the tour for now, it is always available by clicking
+ {' '}
+ Show Tour
+ {' '}
+ at the top of the page.
+
+
+ {
+ setStepIndex(stepIndex + 1)
+ }
+ }
+ >
+ Take the tour
+
+ {
+ setRunTour(false)
+ setStepIndex(0)
+ }
+ }
+ >
+ Skip for now
+
+
+
+ ),
+ disableBeacon: true,
+ placement: 'center'
+ },
+ {
+ target: '.sidebar__inner',
+ content: (
+
+
+
+ This area contains the filters used when searching for collections
+ (datasets produced by an organization)
+ and their granules (sets of files containing data).
+
+
+ Available filters include keyword search, spatial and temporal bounds,
+ and advanced search options.
+
+
+
+ ),
+ placement: 'right',
+ styles: {
+ tooltip: {
+ width: '400px'
+ },
+ tooltipContent: {
+ fontSize: '14px',
+ textAlign: 'left',
+ lineHeight: '1.5'
+ }
+ }
+ },
+ {
+ target: '.search-form__primary',
+ content: (
+
+
+
+ Search for collections by topic (e.g., "Land Surface Temperature"),
+ by collection name, or by CMR Concept ID.
+
+
+ As you type, suggestions for matching topics and keywords will be
+ displayed. When selected, they will be applied as additional search filters.
+
+
+
+ Find more information about the
+ {' '}
+
+ Common Metadata Repository (CMR)
+
+
+
+
+
+ ),
+ placement: 'right',
+ styles: {
+ tooltip: {
+ width: '400px'
+ },
+ tooltipContent: {
+ fontSize: '14px',
+ textAlign: 'left',
+ lineHeight: '1.5'
+ }
+ }
+ },
+ {
+ target: '.temporal-selection-dropdown',
+ content: (
+
+
+
+ Use the temporal filters to limit search results to a specific date
+ and time range.
+
+
+ A recurring filter can be applied to search a repeating range between
+ specified years.
+
+
+
+ ),
+ placement: 'right',
+ styles: {
+ tooltip: {
+ width: '400px'
+ },
+ tooltipContent: {
+ fontSize: '14px',
+ textAlign: 'left',
+ lineHeight: '1.5'
+ }
+ }
+ },
+ {
+ target: '.spatial-selection-dropdown',
+ content: (
+
+
+
+ Use the spatial filters to limit search results to the specified
+ area of interest.
+
+
+ To set the spatial area using a polygon, rectangle, point and radius,
+ or circle, select an option from the menu and then draw on the map
+ or manually enter coordinates.
+
+
+ Upload a shapefile (KML, KMZ, ESRI, etc.) to set the spatial area with
+ a file.
+
+
+
+ ),
+ placement: 'right',
+ styles: {
+ tooltip: {
+ width: '400px'
+ },
+ tooltipContent: {
+ fontSize: '14px',
+ textAlign: 'left',
+ lineHeight: '1.5'
+ }
+ }
+ },
+ {
+ target: '.search-form__button--advanced-search',
+ content: (
+
+
+
+ Use Advanced Search parameters to filter results using features like
+ Hydrologic Unit Code (HUC) or SWORD River Reach.
+
+
+
+ ),
+ placement: 'right',
+ styles: {
+ tooltip: {
+ width: '400px'
+ },
+ tooltipContent: {
+ fontSize: '14px',
+ textAlign: 'left',
+ lineHeight: '1.5'
+ }
+ }
+ },
+ {
+ target: '.sidebar-browse-portals',
+ content: (
+
+
+
+ Choose a portal to refine search results to a particular area of study,
+ project, or organization.
+
+
+
+ ),
+ placement: 'right',
+ disableScrolling: true,
+ styles: {
+ tooltip: {
+ width: '400px'
+ }
+ }
+ },
+ {
+ target: '.sidebar-section-body',
+ content: (
+
+
+
+ Refine your search further using categories like Features, Keywords,
+ Platforms, Organizations, etc.
+
+
+
+ ),
+ placement: 'right-start',
+ disableScrolling: true,
+ styles: {
+ tooltip: {
+ width: '400px'
+ }
+ }
+ },
+ {
+ target: '.panel-section',
+ content: (
+
+
+
+ A high-level description is displayed for each search result to help you find
+ the right data, including a summary, temporal range, and information about
+ capabilities. To view more information about a collection, click the
+ {' '}
+
+ {' '}
+ icon.
+
+
+ Add granules to a project and customize options before accessing the data.
+ To add a collection to your project, click the
+ {' '}
+
+ {' '}
+ icon.
+ To add individual granules to a project, click on a search result to view and
+ add its granules.
+
+
+
+ ),
+ placement: 'right',
+ styles: {
+ tooltip: {
+ width: '400px'
+ }
+ },
+ floaterProps: {
+ disableFlip: true,
+ offset: 10
+ }
+ },
+ {
+ target: '.panels__handle',
+ content: (
+
+
+
+ To make more room to view the map, the search results can be resized by clicking
+ or dragging the bar above. The panel can be hidden or shown by clicking the
+ handle or using the
+ {' '}
+ ]
+ {' '}
+ key.
+
+
+
+ All keyboard shortcuts can be displayed by pressing the
+ {' '}
+ ?
+ {' '}
+ key at any time.
+
+
+
+
+ ),
+ placement: 'right',
+ styles: {
+ tooltip: {
+ width: '400px'
+ }
+ }
+ },
+ {
+ target: '.right-overlay',
+ content: (
+
+
+
+ Pan the map by clicking and dragging, and zoom by using the scroll wheel or
+ map tools.
+
+
+ When a collection is selected, the granules will be displayed on the map,
+ along with any available GIBS imagery. When a granule is focused on the
+ map, any additional thumbnails will be displayed.
+
+
+
+ ),
+ placement: 'left',
+ styles: {
+ tooltip: {
+ width: '400px',
+ textAlign: 'left'
+ }
+ }
+ },
+ {
+ target: '.leaflet-bottom.leaflet-right',
+ content: (
+
+
+
+ Use the map tools to switch map projections, draw, edit, or remove spatial
+ bounds, zoom the map, or select the base map.
+
+
+
+ ),
+ placement: 'left',
+ styles: {
+ tooltip: {
+ width: '400px'
+ },
+ tooltipContent: {
+ fontSize: '14px',
+ textAlign: 'left',
+ lineHeight: '1.5'
+ }
+ }
+ },
+ {
+ target: '.secondary-toolbar__begin-tour-button',
+ content: (
+
+
+
+ You can replay this tour anytime by clicking
+ {' '}
+ Show Tour
+ .
+
+
+ {
+ setStepIndex(stepIndex - 1)
+ }
+ }
+ >
+ Previous
+
+ {
+ setStepIndex(stepIndex + 1)
+ }
+ }
+ >
+ Finish Tour
+
+
+
+ ),
+ placement: 'right',
+ styles: {
+ tooltip: {
+ width: '400px'
+ }
+ }
+ },
+ {
+ target: '.search',
+ content: (
+
+
+ Want to learn more?
+
+
+ Check out our latest webinar where you will see a hands-on example of
+ how to search for data in Earthdata Search.
+
+
+
+
+
+
+
+ Discover and Access Earth Science Data Using Earthdata Search
+
+
+
+ Watch the webinar
+
+
+
+
+
+ Find more information here:
+
+
+
+ Earthdata Search wiki
+
+
+
+
+ Earthdata Search FAQs
+
+
+
+ ),
+ disableBeacon: true,
+ placement: 'center',
+ styles: {
+ tooltip: {
+ width: '600px',
+ padding: '20px'
+ },
+ tooltipContent: {
+ fontSize: '16px'
+ }
+ }
+ }
+ ]
+
+ const handleJoyrideCallback = (data) => {
+ const {
+ action, index, status, type
+ } = data
+
+ if ([STATUS.FINISHED, STATUS.SKIPPED, STATUS.PAUSED].includes(status)
+ || action === ACTIONS.CLOSE) {
+ setRunTour(false)
+ setStepIndex(0)
+ } else if (type === 'step:after') {
+ setStepIndex(action === ACTIONS.NEXT ? index + 1 : index - 1)
+ }
+
+ if (type === 'step:before') {
+ const element = document.querySelector('.sidebar-section-body')
+ if (element) element.scrollTop = 0
+ }
+ }
+
+ return (
+
+ )
+}
+
+SearchTour.propTypes = {
+ runTour: PropTypes.bool.isRequired,
+ setRunTour: PropTypes.func.isRequired
+}
+
+export default SearchTour
diff --git a/static/src/js/components/Tour/SearchTour.scss b/static/src/js/components/Tour/SearchTour.scss
new file mode 100644
index 0000000000..d2ff03152a
--- /dev/null
+++ b/static/src/js/components/Tour/SearchTour.scss
@@ -0,0 +1,103 @@
+:root {
+ --color-white: #ffffff;
+ --color-black: #000000;
+ --color-heading: #0063A6;
+}
+
+.button-tour-finish {
+ min-width: 90px;
+ }
+
+.step-counter-text {
+ font-family: "DM Mono", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+ font-size: 12px;
+ margin-bottom: 15px;
+ color: #9a989a;
+ text-align: left;
+}
+
+
+.text-icon {
+ display: inline-block;
+ vertical-align: center;
+ margin-top: -2px;
+ height: 0.85rem;
+ width: 0.85rem;
+}
+
+.tour-heading {
+ font-size: 14px;
+ margin-bottom: 10px;
+ color: var(--color-black);
+ text-transform: uppercase;
+ letter-spacing: 1px;
+}
+
+kbd {
+ display: inline-block;
+ vertical-align: center;
+ font-size: 0.75em;
+ padding: 2px 3px;
+}
+
+.tour-subheading {
+ font-size: 24px;
+ font-weight: bold;
+ margin-bottom: 20px;
+}
+
+.tour-content {
+ font-size: 16px;
+ margin-bottom: 20px;
+ text-align: left;
+}
+
+.tour-note {
+ font-size: 14px;
+ margin-bottom: 20px;
+ text-align: left;
+}
+.tour-intro-buttons {
+ display: flex;
+ justify-content: flex-end;
+ gap: 15px;
+ position: absolute;
+ bottom: 20px;
+ right: 20px;
+ width: 19rem;
+}
+.tour-buttons {
+ display: flex;
+ justify-content: flex-end;
+ gap: 15px;
+ position: absolute;
+ bottom: 20px;
+ right: 20px;
+ width: 9rem;
+}
+
+.tour-info-box {
+ background-color: #f8f9fa;
+ padding: 1rem;
+ margin-bottom: 15px;
+ border-radius: 4px;
+ font-size: 14px;
+ text-align: left;
+}
+
+.tour-info-box a {
+ color: var(--color-black);
+ text-decoration: underline;
+ font-weight: bold;
+}
+
+.tour-tooltip {
+ width: 600px;
+ padding: 20px;
+ background-color: var(--color-white);
+ border-radius: 10px;
+ font-size: 16px;
+ text-align: center;
+ position: relative;
+ min-height: 200px;
+}
diff --git a/static/src/js/components/Tour/__tests__/SearchTour.test.js b/static/src/js/components/Tour/__tests__/SearchTour.test.js
new file mode 100644
index 0000000000..0ce6eb69c8
--- /dev/null
+++ b/static/src/js/components/Tour/__tests__/SearchTour.test.js
@@ -0,0 +1,27 @@
+import React from 'react'
+import { render, screen } from '@testing-library/react'
+import '@testing-library/jest-dom/extend-expect'
+import SearchTour from '../SearchTour'
+
+// Mock the local storage
+const localStorageMock = {
+ getItem: jest.fn(),
+ setItem: jest.fn(),
+ clear: jest.fn()
+}
+global.localStorage = localStorageMock
+
+describe('SearchTour', () => {
+ const mockSetRunTour = jest.fn()
+
+ beforeEach(() => {
+ jest.clearAllMocks()
+ })
+
+ describe('when rendered', () => {
+ it('displays the tour component', () => {
+ render( )
+ expect(screen.getByTestId('mocked-joyride')).toBeInTheDocument()
+ })
+ })
+})
diff --git a/static/src/js/containers/SecondaryToolbarContainer/SecondaryToolbarContainer.jsx b/static/src/js/containers/SecondaryToolbarContainer/SecondaryToolbarContainer.jsx
index 60a2962087..6f17570a24 100644
--- a/static/src/js/containers/SecondaryToolbarContainer/SecondaryToolbarContainer.jsx
+++ b/static/src/js/containers/SecondaryToolbarContainer/SecondaryToolbarContainer.jsx
@@ -44,7 +44,8 @@ export const SecondaryToolbarContainer = (props) => {
savedProject,
retrieval,
ursProfile,
- onFetchContactInfo
+ onFetchContactInfo,
+ onStartTour
} = props
useEffect(() => {
@@ -66,6 +67,7 @@ export const SecondaryToolbarContainer = (props) => {
retrieval={retrieval}
secondaryToolbarEnabled={secondaryToolbarEnabled}
ursProfile={ursProfile}
+ onStartTour={onStartTour}
/>
)
}
@@ -82,7 +84,8 @@ SecondaryToolbarContainer.propTypes = {
savedProject: PropTypes.shape({}).isRequired,
ursProfile: PropTypes.shape({
first_name: PropTypes.string
- }).isRequired
+ }).isRequired,
+ onStartTour: PropTypes.func.isRequired
}
export default withRouter(
diff --git a/static/src/js/routes/Search/Search.jsx b/static/src/js/routes/Search/Search.jsx
index bcb9dd7906..2f9c63f848 100644
--- a/static/src/js/routes/Search/Search.jsx
+++ b/static/src/js/routes/Search/Search.jsx
@@ -171,69 +171,73 @@ export const Search = ({