From 4f8b0254bb29b02bf9908e4147d5b65217c09b07 Mon Sep 17 00:00:00 2001 From: Carter Date: Tue, 8 Jul 2025 14:07:01 -0600 Subject: [PATCH] added a welcome tour to the signupCallback page --- package-lock.json | 56 ++++++++++++ package.json | 1 + src/common/Tour.tsx | 30 +++++-- src/navigation/SideNavigator.tsx | 8 +- src/pages/Firmware.tsx | 4 +- src/pages/SignupCallback.tsx | 148 +++++++++++++++++++++++++++++-- src/pages/Users.tsx | 2 +- src/providers/auth.tsx | 27 +++--- src/providers/pond/userAPI.tsx | 1 - src/user/UserMenu.tsx | 2 +- 10 files changed, 247 insertions(+), 32 deletions(-) diff --git a/package-lock.json b/package-lock.json index 65ae1cc..840ea14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,6 +49,7 @@ "react-color": "^2.19.3", "react-day-picker": "^9.6.5", "react-dom": "^18.3.1", + "react-emoji-render": "^2.0.1", "react-error-boundary": "^5.0.0", "react-full-screen": "^1.1.1", "react-horizontal-scrolling-menu": "^7.1.1", @@ -9188,6 +9189,15 @@ "node": ">=12" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -9971,6 +9981,18 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -11107,6 +11129,29 @@ "react": "^18.3.1" } }, + "node_modules/react-emoji-render": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/react-emoji-render/-/react-emoji-render-2.0.1.tgz", + "integrity": "sha512-SKtsdwgEf2BFNiE9y4UBFZBWjkRcyWmhMprVly52+J77/zxThcfaQ3sCA0+2LtGJIRMpm4DGWSvyLg72fd1rXQ==", + "license": "MIT", + "dependencies": { + "classnames": "^2.2.5", + "emoji-regex": "^8.0.0", + "lodash.flatten": "^4.4.0", + "prop-types": "^15.5.8", + "string-replace-to-array": "^1.0.1" + }, + "peerDependencies": { + "react": ">=0.14.0", + "react-dom": ">=0.14.0" + } + }, + "node_modules/react-emoji-render/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, "node_modules/react-error-boundary": { "version": "5.0.0", "integrity": "sha512-tnjAxG+IkpLephNcePNA7v6F/QpWLH8He65+DmedchDwg162JZqx4NmbXj0mlAYVVEd81OW7aFhmbsScYfiAFQ==", @@ -12408,6 +12453,17 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-replace-to-array": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string-replace-to-array/-/string-replace-to-array-1.0.3.tgz", + "integrity": "sha512-QIdKvmfXbdFvblXAAz6IIjR7A+C6SU6m2A+e7fE/0EYDC5yfeWNMJQ193fPsW7nG+9q52dv/UjnVrDaNVZXpmQ==", + "license": "MIT", + "dependencies": { + "invariant": "^2.2.1", + "lodash.flatten": "^4.2.0", + "lodash.isstring": "^4.0.1" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.12", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", diff --git a/package.json b/package.json index 67d05d8..afae078 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "react-color": "^2.19.3", "react-day-picker": "^9.6.5", "react-dom": "^18.3.1", + "react-emoji-render": "^2.0.1", "react-error-boundary": "^5.0.0", "react-full-screen": "^1.1.1", "react-horizontal-scrolling-menu": "^7.1.1", diff --git a/src/common/Tour.tsx b/src/common/Tour.tsx index 4781e21..9d328a7 100644 --- a/src/common/Tour.tsx +++ b/src/common/Tour.tsx @@ -1,4 +1,4 @@ -import Joyride, { Step, CallBackProps, STATUS, TooltipRenderProps, Locale } from "react-joyride"; +import Joyride, { Step, CallBackProps, STATUS, TooltipRenderProps, Locale, ACTIONS, EVENTS } from "react-joyride"; import { Button, Card, @@ -10,12 +10,13 @@ import { Tooltip, IconButton, Theme, - useTheme + useTheme, } from "@mui/material"; import { KeyboardArrowLeft, KeyboardArrowRight, Close as CloseIcon } from "@mui/icons-material"; import classNames from "classnames"; import { grey } from "@mui/material/colors"; import { makeStyles } from "@mui/styles"; +import { CSSProperties } from "react"; const useStyles = makeStyles((theme: Theme) => { return ({ @@ -46,6 +47,10 @@ const useStyles = makeStyles((theme: Theme) => { }) }); +export interface TourStep extends Step { + onNext?: () => void; // Define onNext as an optional function +} + const StepContainer = (props: TooltipRenderProps, numSteps: number, skip: () => void) => { const { continuous, @@ -114,18 +119,30 @@ const StepContainer = (props: TooltipRenderProps, numSteps: number, skip: () => interface Props { run: boolean; - steps: Step[]; + steps: TourStep[]; endTourCallback: () => void; + spotlightStyle?: CSSProperties, + setStepIndex?: React.Dispatch>, } export default function Tour(props: Props) { - const { run, steps, endTourCallback } = props; + const { run, steps, endTourCallback, setStepIndex, spotlightStyle } = props; const theme = useTheme(); const handleTourCallback = (data: CallBackProps) => { - const { status } = data; + const { action, index, status, type } = data; const finishedStatuses: string[] = [STATUS.FINISHED, STATUS.SKIPPED]; + if (setStepIndex) setStepIndex(index) + + if (action === ACTIONS.NEXT && type === EVENTS.STEP_AFTER) { + // Safely access the step and call onNext if it exists + const currentStep = steps[index]; + if (currentStep.onNext) { + currentStep.onNext(); + } + } + if (finishedStatuses.includes(status)) { endTourCallback(); } @@ -150,6 +167,9 @@ export default function Tour(props: Props) { primaryColor: theme.palette.primary.main, textColor: theme.palette.text.primary, zIndex: theme.zIndex.modal + }, + spotlight: { + ...spotlightStyle } }} /> diff --git a/src/navigation/SideNavigator.tsx b/src/navigation/SideNavigator.tsx index 38a10cb..f71ca9c 100644 --- a/src/navigation/SideNavigator.tsx +++ b/src/navigation/SideNavigator.tsx @@ -221,7 +221,7 @@ export default function SideNavigator(props: Props) { {(isAg || isStreamline || user.hasFeature("admin")) && ( goTo("/bins")} classes={getClasses("/bin")} > @@ -277,7 +277,7 @@ export default function SideNavigator(props: Props) { {(isMiVent || user.hasFeature("admin")) && ( goTo("/mines")} classes={getClasses("/mine")} > @@ -330,7 +330,7 @@ export default function SideNavigator(props: Props) { } goTo("/teams")} classes={getClasses("/team")} > @@ -342,7 +342,7 @@ export default function SideNavigator(props: Props) { goTo("/users")} classes={getClasses("/user")} > diff --git a/src/pages/Firmware.tsx b/src/pages/Firmware.tsx index b3a7c58..1ae97af 100644 --- a/src/pages/Firmware.tsx +++ b/src/pages/Firmware.tsx @@ -12,7 +12,7 @@ import { TypeBackground } from "@mui/material"; import { blue, green, orange, red } from "@mui/material/colors"; -import { createStyles, makeStyles } from "@mui/styles"; +import { makeStyles } from "@mui/styles"; //import { TypeBackground } from "@material-ui/core/styles/createPalette"; import { ArrowUpward, @@ -267,7 +267,7 @@ export default function Firmware() { if (selected) { firmwareAPI .removeFirmware(selected.settings.platform, selected.settings.version) - .then((response: any) => { + .then(() => { success("Successfully removed firmware"); }) .catch((err: any) => { diff --git a/src/pages/SignupCallback.tsx b/src/pages/SignupCallback.tsx index 00b2c51..a22dbfc 100644 --- a/src/pages/SignupCallback.tsx +++ b/src/pages/SignupCallback.tsx @@ -1,26 +1,36 @@ import { useDeviceAPI, useMobile, useUserAPI } from "hooks"; import PageContainer from "./PageContainer"; import { useEffect, useState } from "react"; -import { Button, CircularProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Divider } from "@mui/material"; -import { useBinAPI, useTeamAPI } from "providers"; +import { Button, CircularProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Divider, Typography } from "@mui/material"; +import { useBinAPI, useGlobalState, useTeamAPI } from "providers"; import { CheckCircleOutline } from "@mui/icons-material"; import { green } from "@mui/material/colors"; +import Tour, { TourStep } from "common/Tour"; +import Emoji from "react-emoji-render"; +import { getWhitelabel, IsAdaptiveAgriculture } from "services/whiteLabel"; +// import { color } from "framer-motion"; -interface Props { +// interface Props { -} +// } -export default function SignupCallback (props: Props) { +// export default function SignupCallback (props: Props) { +export default function SignupCallback () { const userAPI = useUserAPI(); const teamAPI = useTeamAPI() const deviceAPI = useDeviceAPI(); const binAPI = useBinAPI(); const isMobile = useMobile() + const [{ user }] = useGlobalState(); + const whiteLabel = getWhitelabel() + const [loading, setLoading] = useState(false) const [signupDialogOpen, setSignupDialogOpen] = useState(true) const [hasTeams, setHasTeams] = useState(false) const [hasDevices, setHasDevices] = useState(false) const [hasBins, setHasBins] = useState(false) + const [isTourRunning, setIsTourRunning] = useState(false); + const [stepIndex, setStepIndex] = useState(0) const [doneBins, setDoneBins] = useState(false) const [doneTeams, setDoneTeams] = useState(false) @@ -28,7 +38,7 @@ export default function SignupCallback (props: Props) { useEffect(() => { setLoading(true) - userAPI.completeSignup().then(resp => { + userAPI.completeSignup().then(() => { teamAPI.listTeams(1, 0).then(resp => { setHasTeams(resp.data.teams.length > 0) }).finally(() => setDoneTeams(true)) @@ -55,13 +65,131 @@ export default function SignupCallback (props: Props) { } {hasTeams && "Teams found!"} +
{hasDevices && "Devices found!"} +
{hasBins && "Bins found!"}
) } + const getTourSteps = (): TourStep[] => { + let steps: TourStep[] = [ + { + title: ( + <> + Welcome to {whiteLabel.name} + + + ), + content: ( + <> + + {"Hello " + user.name()} + + + + Thank you for signing up! Let me give you the tour + + + + ), + target: "body", + placement: "center", + disableBeacon: true, + }, + { + title: "User Menu", + content: ( + <> + + In the user menu, you can make changes to your profile, and adjust unit preferences. + +
+ + If you are part of a team, you can select it here as well. + + + ), + target: "#tour-user-menu", + placement: "bottom", + disableBeacon: true, + // onNext: () => setDoneFirst(true) + }, + // { + // title: + // } + ]; + if (hasTeams) steps.push({ + title: "Teams", + content: ( + <> + + Here, you can view your team(s), access their settings, and choose your favourite. + + + ), + target: "#tour-teams", + placement: "right", + // spotlightPadding: 50, + }) + steps.push({ + title: "Devices", + content: ( + <> + + You can view your devices and their readings here. + +
+ + If you are viewing as a team, your team's devices will be displayed here instead. + + + ), + target: "#tour-dashboard", + placement: "right" + }) + if (IsAdaptiveAgriculture()) steps.push({ + title: "Bins", + content: ( + <> + + You can view your bins and their inventory here. + +
+ + If you are viewing as a team, your team's bins will be displayed here instead. + + + ), + target: "#tour-bins", + placement: "right", + disableBeacon: true, + }) + return steps; + }; + + const endTour = () => { + setIsTourRunning(false); + // if (user) { + // user.status.finishedBinIntro = moment().toJSON(); + // userAPI + // .updateUser(userID, user.protobuf()) + // .then(() => dispatch({ key: "user", value: user })) + // .catch((err: any) => error(err)); + // } + }; + + const closeDialog = () => { + setSignupDialogOpen(false) + setIsTourRunning(true) + } + + useEffect(() => { + console.log(stepIndex) + }, [stepIndex]) + return ( @@ -72,12 +200,18 @@ export default function SignupCallback (props: Props) { { content() } {loadingComplete() && - } + ) } \ No newline at end of file diff --git a/src/pages/Users.tsx b/src/pages/Users.tsx index 00ef9f7..296c60a 100644 --- a/src/pages/Users.tsx +++ b/src/pages/Users.tsx @@ -349,7 +349,7 @@ export default function Users() { } return ( - + title="Users" rows={rows} diff --git a/src/providers/auth.tsx b/src/providers/auth.tsx index f7ee585..2a8f6bb 100644 --- a/src/providers/auth.tsx +++ b/src/providers/auth.tsx @@ -9,7 +9,13 @@ interface Props extends PropsWithChildren{ export default function AuthWrapper(props: Props) { const { children, setToken } = props; - const { isLoading, loginWithRedirect, isAuthenticated, getAccessTokenSilently, getIdTokenClaims } = useAuth0(); + const { + isLoading, + loginWithRedirect, + isAuthenticated, + getAccessTokenSilently, + // getIdTokenClaims + } = useAuth0(); useEffect(() => { @@ -42,8 +48,8 @@ export default function AuthWrapper(props: Props) { }, [isAuthenticated, loginWithRedirect, isLoading]); useEffect(() => { - const url = new URL(window.location.href); - const isSignupCallback = url.pathname.includes("/signupCallback"); + // const url = new URL(window.location.href); + // const isSignupCallback = url.pathname.includes("/signupCallback"); if (isAuthenticated) { getAccessTokenSilently().then(t => { if (t.length < 1) { @@ -54,14 +60,13 @@ export default function AuthWrapper(props: Props) { // No token, go back to login loginWithRedirect() }) - if (isSignupCallback) { - console.log("IS SIGNUP") - getIdTokenClaims().then(id_claims => { - console.log(id_claims) - console.log(id_claims?.nickname) - if (id_claims) console.log(Object.keys(id_claims)) - }) - } + // if (isSignupCallback) { + // getIdTokenClaims().then(id_claims => { + // console.log(id_claims) + // console.log(id_claims?.nickname) + // if (id_claims) console.log(Object.keys(id_claims)) + // }) + // } } }, [setToken, isAuthenticated]) diff --git a/src/providers/pond/userAPI.tsx b/src/providers/pond/userAPI.tsx index 21befb2..0d33b2e 100644 --- a/src/providers/pond/userAPI.tsx +++ b/src/providers/pond/userAPI.tsx @@ -67,7 +67,6 @@ export default function UserProvider(props: PropsWithChildren) { request.id = user.id() request.name = user.name() post(pondURL("/signups"), request).then(res => { - console.log(res) resolve(pond.CompleteSignupUserResponse.create()) }) }) diff --git a/src/user/UserMenu.tsx b/src/user/UserMenu.tsx index 2b41a6f..c7b8ab3 100644 --- a/src/user/UserMenu.tsx +++ b/src/user/UserMenu.tsx @@ -228,7 +228,7 @@ export default function UserMenu() { */} {hasTeams && ( - +