elrumordelaluz / reactour

Tourist Guide into your React Components
https://react.tours
MIT License
3.85k stars 351 forks source link

How can i integrate reactour v2 with nextjs? #571

Open Farhatdjehan opened 1 year ago

Farhatdjehan commented 1 year ago

recently i just install reactour v2 on my nextjs apps, how can i implement it?

lullabyX commented 1 year ago

Hey @Farhatdjehan I just integrated reactour in my work last week. Follow the documentation from here. My recommendation is follow the type definition to see what props are available. General Steps are:

  1. Create the array of step (type StepType)
  2. In _app.tsx wrap with
    <TourProvider steps={steps}>
    <Component {...pageProps}/>
    </TourProvider>

If you have more specific question, feel free to reach out. Cheers

Farhatdjehan commented 1 year ago

many thanks to you bro!, i already try your answer but i found new problem.. my reactour doesnt update steps even the pages was changing... here's my code.. @lullabyX image

ghost commented 1 year ago

Any ideas how to get it to wait for the page to fully hydrate and render for app router on nextjs?

Trying to use it for multiple pages but the tour won't wait for the selected element to load in.

ghost commented 1 year ago

Any ideas how to get it to wait for the page to fully hydrate and render for app router on nextjs?

Trying to use it for multiple pages but the tour won't wait for the selected element to load in.

Figured out a very hacky way of doing it. Here's an example using a useEffect in a client component with tailwind:

"use client"
// React
import { useEffect, useState } from "react";
// React Tour
import { useTour } from "@reactour/tour";

const ExamplePage = () => {
    ...

    // Start the tour back up again - This must be done on every page navigation
    const { setIsOpen, isOpen, currentStep } = useTour();
    const [loading, setLoading] = useState(false);
    useEffect(() => {
        if (isOpen) {
            setLoading(true);
            setIsOpen(false);
            setTimeout(() => {
                setIsOpen(true);
                setLoading(false);
            }, 1000);
        }
    }, []);

    return (
        <>
            {/* React Tour, Creates Seamless Mask Whilst Restarting Tour */}
            {loading && (
                <div className="h-screen w-screen z-[1300] absolute left-0 top-0 opacity-70 bg-black"></div>
            )}

            ...
        </>
    );
};
ghost commented 1 year ago

many thanks to you bro!, i already try your answer but i found new problem.. my reactour doesnt update steps even the pages was changing... here's my code.. @lullabyX image

Here's a reasonably complex answer to your solution in a client component with the app router.

This is working in production and works on page transitions. However, you'll need to turn the tour on and off on each page transition back and forth (see my hacky solution above 👆).

"use client";

// Next Imports
import { useRouter } from "next/navigation";
// React
import React, { useState } from "react";
// MUI hook
import { useMediaQuery } from "@mui/material";

// React Tour
import { TourProvider } from "@reactour/tour";
// Auth
import { useSession } from "next-auth/react";
// Context
import { useUserAppTourContext } from "../../contexts/userAppTourContext";

export default function UserAppTour({ children, mobileOpen, setMobileOpen }) {
    // React Tour config
    const [step, setStep] = useState(0);
    // Test if the device is a mobile
    const isMobile = useMediaQuery("(max-width:1280px)");

    // Tour Steps
    const reactTourSteps = [
        // Step 0 -/
        {
            selector: isMobile
                ? "[data-tour='home_page_mobile_link']"
                : "[data-tour='home_page_link']",
            content: "Click the home button to return to the home page.",
        },
        // Step 1
        {
            selector: "[data-tour='enter_reg_banner']",
            content:
                "Search for your vehicle by clicking ENTER REG. You will then be directed to enter the vehicle's registration number.",
        },
        // Step 2 - /vrm
        {
            selector: "[data-tour='vehicle_registration_input']",
            content:
                "Enter your vehicle registration in the ENTER REG input. After entering your registration, either press enter on your keyboard or click the submit button.",
        },
        // Step 3 - /vrm/CE65PJO
        {
            selector: "[data-tour='vehicle_details_button']",
            content:
                "Click the MORE DETAILS button to view more information about your vehicle after submitting your reg.",
        },
        // Step 4 - /
        {
            selector: "[data-tour='search_box']",
            content:
                "Once your vehicle has been selected, you can search for a part by using the search bar.",
        },
        // Step 5
        {
            selector: "[data-tour='service_kit_banner']",
            content:
                "Click the SERVICE KIT banner to quickly generate a service kit for your selected vehicle.",
            position: "bottom",
        },
        // Step 6
        {
            selector: "[data-tour='brake_kit_banner']",
            content:
                "Click the BRAKE KIT banner to quickly generate a brake kit for your selected vehicle.",
            position: "bottom",
        },
        // Step 7
        {
            selector: "[data-tour='featured_categories_tab']",
            content:
                "You can also select a part by using our FEATURED part section.",
        },
        // Step 8
        {
            selector: "[data-tour='all_categories_tab']",
            content:
                "If you want to view all of our categories, click the ALL CATEGORIES button.",
            position: "bottom",
        },
        // Step 9 - /parts/Air%2520Filter
        {
            selector: "[data-tour='part_item_selection']",
            content:
                "We offer a range of prices for different brands or parts. Choose whichever brand you prefer.",
        },
        // Step 10 - /parts/Air%2520Filter
        {
            selector: isMobile
                ? "[data-tour='part_filter_button_mobile']"
                : "[data-tour='part_filter_button']",
            content:
                "Click the filter button to open up a menu where you can filter the parts by brand or quality.",
        },
        // Step 11 - /parts/Air%2520Filter
        {
            selector: "[data-tour='part_basket_button']",
            content:
                "Once you've selected your part, click the ADD TO BASKET button to add it to your basket.",
        },
        // Step 12 - /
        {
            selector: isMobile
                ? "[data-tour='vrm_page_link_mobile']"
                : "[data-tour='vrm_page_link']",
            content:
                "Click the Vehicle button to view your vehicle history or find a new vehicle.",
        },
        // Step 13 - /
        {
            selector: isMobile
                ? "[data-tour='order_page_mobile_link']"
                : "[data-tour='order_page_link']",
            content:
                "Click the Orders button to view your previous orders. You can search by document number or date range for all orders/credits.",
        },
        // Step 14
        {
            selector: isMobile
                ? "[data-tour='basket_page_mobile_link']"
                : "[data-tour='basket_page_link']",
            content:
                "Click the Basket button to view your basket. Here you can amend items already added to the basket or search new items.",
        },
        // Step 15
        {
            selector: isMobile
                ? "[data-tour='feedback_page_link_mobile']"
                : "[data-tour='feedback_page_link']",
            content:
                "Click the Send feedback button to view the feedback page. Here you can let us know your thoughts on the app experience.",
        },
        // Step 16
        {
            selector: isMobile
                ? "[data-tour='account_page_link_mobile']"
                : "[data-tour='account_page_link']",
            content:
                "Click the Account button to view your account page. Here you can view your account details and change your password etc.",
        },
        // Step 17
        {
            selector: isMobile
                ? "[data-tour='settings_page_link_mobile']"
                : "[data-tour='settings_page_link']",
            content:
                "Click the Settings button to view the settings page. Here you can change various app settings like, whether to view line codes and toggle VAT on or off.",
        },
        // Step 18
        {
            selector: isMobile
                ? "[data-tour='sign_out_link_mobile']"
                : "[data-tour='sign_out_link']",
            content: "Click the Sign out button to sign out of the app.",
        },
    ];

    // Context
    const { turnUserAppIntroOff } = useUserAppTourContext();

    // Set up router
    const router = useRouter();

    const setCurrentStep = (step) => {
        // You need to add route changes before and after route changes
        // This is to allow users to go back and forth between pages
        switch (step) {
            case 0:
                router.push("/");
                turnUserAppIntroOff();
                break;
            case 1:
                router.push("/");
                break;
            case 2:
                router.push("/vrm");
                break;
            case 3:
                router.push("/vrm/CE65PJO");
                break;
            case 4:
                router.push("/");
                break;
            case 5:
                if (isMobile) {
                    const featuredCategoriesTab = document.querySelector(
                        "[data-tour='featured_categories_tab']"
                    );
                    featuredCategoriesTab.scrollIntoView({
                        behavior: "smooth",
                        block: "center",
                        inline: "center",
                    });
                }
            case 8:
                router.push("/");
                break;
            case 9:
                router.push("/parts/Air%2520Filter");
                break;
            case 10:
                if (isMobile) {
                    const partBasketButton = document.querySelector(
                        "[data-tour='part_basket_button']"
                    );
                    partBasketButton.scrollIntoView({
                        behavior: "smooth",
                        block: "center",
                        inline: "center",
                    });
                }
            case 11:
                router.push("/parts/Air%2520Filter");
                break;
            case 12:
                if (isMobile) {
                    setMobileOpen(!mobileOpen);
                }
                router.push("/");
                break;
            case 13:
                if (isMobile) {
                    setMobileOpen(!mobileOpen);
                }
            case 15:
                if (isMobile) {
                    setMobileOpen(!mobileOpen);
                }
            default:
                break;
        }
        setStep(step);
    };

    // Return a fragment if there is no current user
    const { data: session } = useSession();
    const isLoggedIn = session?.user;
    const appTourEnabled = session?.user?.hasUserTourEnabled;

    // TODO: add appTourEnabled check back
    if (!isLoggedIn) {
        return <>{children}</>;
    }

    // Styling
    const borderRadius = 10;

    return (
        <TourProvider
            className="w-64 sm:w-full ml-8 my-4"
            currentStep={step}
            showDots={false}
            disableDotsNavigation
            scrollSmooth
            setCurrentStep={setCurrentStep}
            steps={reactTourSteps}
            styles={{
                popover: (base) => ({
                    ...base,
                    "--reactour-accent": "#ef5a3d",
                    borderRadius: borderRadius,
                    marginRight: "2rem",
                    zIndex: 100000,
                }),
                maskArea: (base) => ({ ...base, rx: borderRadius }),
            }}>
            {children}
        </TourProvider>
    );
}