diff --git a/package-lock.json b/package-lock.json index 9ccac09..eded9dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "qortal-go", - "version": "0.3.6", + "version": "0.3.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "qortal-go", - "version": "0.3.6", + "version": "0.3.8", "dependencies": { "@capacitor/android": "^6.1.2", "@capacitor/app": "^6.0.1", @@ -79,6 +79,7 @@ "slate-react": "^0.109.0", "tippy.js": "^6.3.7", "tiptap-extension-resize-image": "^1.1.8", + "ts-key-enum": "^2.0.12", "vite-plugin-top-level-await": "^1.4.4", "vite-plugin-wasm": "^3.3.0" }, @@ -13285,6 +13286,11 @@ "typescript": ">=4.2.0" } }, + "node_modules/ts-key-enum": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/ts-key-enum/-/ts-key-enum-2.0.13.tgz", + "integrity": "sha512-zixs6j8+NhzazLUQ1SiFrlo1EFWG/DbqLuUGcWWZ5zhwjRT7kbi1hBlofxdqel+h28zrby2It5TrOyKp04kvqw==" + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", diff --git a/package.json b/package.json index bbd4b83..b4dcf8e 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,8 @@ "tippy.js": "^6.3.7", "tiptap-extension-resize-image": "^1.1.8", "vite-plugin-top-level-await": "^1.4.4", - "vite-plugin-wasm": "^3.3.0" + "vite-plugin-wasm": "^3.3.0", + "ts-key-enum": "^2.0.12" }, "devDependencies": { "@testing-library/dom": "^10.3.0", diff --git a/src/App.tsx b/src/App.tsx index 762782d..4f8baa2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -45,6 +45,8 @@ import CloseIcon from "@mui/icons-material/Close"; import { FilePicker } from '@capawesome/capacitor-file-picker'; import './utils/seedPhrase/RandomSentenceGenerator'; import { useFetchResources } from "./common/useFetchResources"; +import HelpIcon from '@mui/icons-material/Help'; + import { createAccount, generateRandomSentence, @@ -121,6 +123,8 @@ import { openIndexedDB, showSaveFilePicker } from "./components/Apps/useQortalM import { fileToBase64 } from "./utils/fileReading"; import { handleGetFileFromIndexedDB } from "./utils/indexedDB"; import { Wallets } from "./Wallets"; +import { useHandleTutorials } from "./components/Tutorials/useHandleTutorials"; +import { Tutorials } from "./components/Tutorials/Tutorials"; type extStates = @@ -302,7 +306,12 @@ export const resumeAllQueues = () => { }); }; +const defaultValuesGlobal = { + openTutorialModal: null, + setOpenTutorialModal: ()=> {} +} export const MyContext = createContext(defaultValues); +export const GlobalContext = createContext(defaultValuesGlobal); export let globalApiKey: string | null = null; @@ -391,6 +400,7 @@ function App() { const [hasSettingsChanged, setHasSettingsChanged] = useRecoilState( hasSettingsChangedAtom ); + const {showTutorial, openTutorialModal, shownTutorialsInitiated, setOpenTutorialModal} = useHandleTutorials() const holdRefExtState = useRef("not-authenticated"); const isFocusedRef = useRef(true); const { isShow, onCancel, onOk, show, message } = useModal(); @@ -458,6 +468,16 @@ function App() { } } + useEffect(()=> { + if(!shownTutorialsInitiated) return + if(extState === 'not-authenticated'){ + showTutorial('create-account') + } else if(extState === "create-wallet" && walletToBeDownloaded){ + showTutorial('important-information') + } else if(extState === "authenticated"){ + showTutorial('getting-started') + } + }, [extState, walletToBeDownloaded, shownTutorialsInitiated]) useEffect(() => { // Attach a global event listener for double-click const handleDoubleClick = () => { @@ -1651,7 +1671,7 @@ function App() { ); }; - +console.log('openTutorialModal3', openTutorialModal) return ( + + {extState === "not-authenticated" && ( {renderProfile()} + + {extState === "create-wallet" && walletToBeDownloaded && ( + { + showTutorial('important-information', true) + }} sx={{ + position: 'fixed', + bottom: '25px', + right: '25px' + }}> + + + )} ); } diff --git a/src/ExtStates/NotAuthenticated.tsx b/src/ExtStates/NotAuthenticated.tsx index 9e3b5db..c6f5806 100644 --- a/src/ExtStates/NotAuthenticated.tsx +++ b/src/ExtStates/NotAuthenticated.tsx @@ -1,9 +1,10 @@ -import React, { useCallback, useEffect, useRef, useState } from "react"; +import React, { useCallback, useContext, useEffect, useRef, useState } from "react"; import { Spacer } from "../common/Spacer"; import { CustomButton, TextItalic, TextP, TextSpan } from "../App-styles"; import { Box, Button, + ButtonBase, Checkbox, Dialog, DialogActions, @@ -21,6 +22,8 @@ import Info from "../assets/svgs/Info.svg"; import { CustomizedSnackbars } from "../components/Snackbar/Snackbar"; import { set } from "lodash"; import { cleanUrl, isUsingLocal } from "../background"; +import HelpIcon from '@mui/icons-material/Help'; +import { GlobalContext } from "../App"; export const manifestData = { version: "0.3.8", @@ -48,6 +51,8 @@ export const NotAuthenticated = ({ const [currentNode, setCurrentNode] = React.useState({ url: "http://127.0.0.1:12391", }); + const { showTutorial } = useContext(GlobalContext); + const [importedApiKey, setImportedApiKey] = React.useState(null); //add and edit states const [url, setUrl] = React.useState("http://"); @@ -675,6 +680,17 @@ export const NotAuthenticated = ({ )} + { + showTutorial('create-account', true) + }} sx={{ + position: 'fixed', + bottom: '25px', + right: '25px' + }}> + + ); }; diff --git a/src/common/useFetchResources.tsx b/src/common/useFetchResources.tsx index 2180966..2390d8b 100644 --- a/src/common/useFetchResources.tsx +++ b/src/common/useFetchResources.tsx @@ -1,10 +1,11 @@ -import React, { useCallback } from 'react'; +import React, { useCallback, useRef } from 'react'; import { useRecoilState } from 'recoil'; import { resourceDownloadControllerAtom } from '../atoms/global'; import { getBaseApiReact } from '../App'; export const useFetchResources = () => { const [resources, setResources] = useRecoilState(resourceDownloadControllerAtom); + const intervalId = useRef(null) const downloadResource = useCallback(({ service, name, identifier }, build) => { setResources((prev) => ({ @@ -21,9 +22,10 @@ export const useFetchResources = () => { let isCalling = false; let percentLoaded = 0; let timer = 24; + let tries = 26; let calledFirstTime = false - const intervalId = setInterval(async () => { + const callFunction = async ()=> { if (isCalling) return; isCalling = true; @@ -40,6 +42,24 @@ export const useFetchResources = () => { }, }); res = await resCall.json() + if(tries > 18 ){ + if(intervalId?.current){ + clearInterval(intervalId?.current) + } + setResources((prev) => ({ + ...prev, + [`${service}-${name}-${identifier}`]: { + ...(prev[`${service}-${name}-${identifier}`] || {}), + status: { + ...res, + status: 'FAILED_TO_DOWNLOAD', + }, + }, + })); + return + } + tries = tries + 1 + } @@ -103,7 +123,10 @@ export const useFetchResources = () => { // Check if progress is 100% and clear interval if true if (res?.status === 'READY') { - clearInterval(intervalId); + if(intervalId.current){ + clearInterval(intervalId.current); + + } // Update Recoil state for completion setResources((prev) => ({ @@ -114,7 +137,12 @@ export const useFetchResources = () => { }, })); } - }, !calledFirstTime ? 100 :5000); + } + callFunction() + intervalId.current = setInterval(async () => { + callFunction() + }, 5000); + } catch (error) { console.error('Error during resource fetch:', error); } diff --git a/src/components/Apps/Apps.tsx b/src/components/Apps/Apps.tsx index 6e013be..3456c6a 100644 --- a/src/components/Apps/Apps.tsx +++ b/src/components/Apps/Apps.tsx @@ -1,7 +1,7 @@ -import React, { useEffect, useMemo, useRef, useState } from "react"; +import React, { useContext, useEffect, useMemo, useRef, useState } from "react"; import { AppsHome } from "./AppsHome"; import { Spacer } from "../../common/Spacer"; -import { getBaseApiReact } from "../../App"; +import { GlobalContext, getBaseApiReact } from "../../App"; import { AppInfo } from "./AppInfo"; import { executeEvent, @@ -25,9 +25,15 @@ export const Apps = ({ mode, setMode, show , myName}) => { const [selectedTab, setSelectedTab] = useState(null); const [isNewTabWindow, setIsNewTabWindow] = useState(false); const [categories, setCategories] = useState([]) + const { showTutorial } = useContext(GlobalContext); + const iframeRefs = useRef({}); - + useEffect(()=> { + if(show){ + showTutorial('qapps') + } + }, [show]) const myApp = useMemo(()=> { return availableQapps.find((app)=> app.name === myName && app.service === 'APP') diff --git a/src/components/Apps/AppsHome.tsx b/src/components/Apps/AppsHome.tsx index 7195f74..11cd22d 100644 --- a/src/components/Apps/AppsHome.tsx +++ b/src/components/Apps/AppsHome.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useState } from "react"; +import React, { useContext, useMemo, useState } from "react"; import { AppCircle, AppCircleContainer, @@ -9,16 +9,20 @@ import { } from "./Apps-styles"; import { Avatar, Box, ButtonBase, Input } from "@mui/material"; import { Add } from "@mui/icons-material"; -import { getBaseApiReact, isMobile } from "../../App"; +import { GlobalContext, getBaseApiReact, isMobile } from "../../App"; import LogoSelected from "../../assets/svgs/LogoSelected.svg"; import { executeEvent } from "../../utils/events"; import { SortablePinnedApps } from "./SortablePinnedApps"; import { Spacer } from "../../common/Spacer"; import ArrowOutwardIcon from '@mui/icons-material/ArrowOutward'; import { extractComponents } from "../Chat/MessageDisplay"; +import HelpIcon from '@mui/icons-material/Help'; +import { useHandleTutorials } from "../Tutorials/useHandleTutorials"; export const AppsHome = ({ setMode, myApp, myWebsite, availableQapps }) => { const [qortalUrl, setQortalUrl] = useState('') + const { showTutorial } = useContext(GlobalContext); + const openQortalUrl = ()=> { try { @@ -40,8 +44,24 @@ export const AppsHome = ({ setMode, myApp, myWebsite, availableQapps }) => { sx={{ justifyContent: "flex-start", + position: 'relative' }} > + { + + showTutorial('qapps', true) + + + }} > + + diff --git a/src/components/Embeds/AttachmentEmbed.tsx b/src/components/Embeds/AttachmentEmbed.tsx index 86c5062..7f4d0f1 100644 --- a/src/components/Embeds/AttachmentEmbed.tsx +++ b/src/components/Embeds/AttachmentEmbed.tsx @@ -297,7 +297,7 @@ export const AttachmentCard = ({ )} - {resourceDetails && resourceDetails?.status?.status !== 'READY' && ( + {resourceDetails && resourceDetails?.status?.status !== 'READY' && resourceDetails?.status?.status !== 'FAILED_TO_DOWNLOAD' && ( <> = ({ + poster, + name, + identifier, + service, + autoplay = true, + from = null, + customStyle = {}, + node +}) => { + + const keyIdentifier = useMemo(()=> { + + if(name && identifier && service){ + return `${service}-${name}-${identifier}` + } else { + return undefined + } + }, [service, name, identifier]) + const download = useRecoilValue(resourceKeySelector(keyIdentifier)); + const { downloadResource } = useContext(GlobalContext); + + const videoRef = useRef(null) + const [playing, setPlaying] = useState(false) + const [volume, setVolume] = useState(1) + const [mutedVolume, setMutedVolume] = useState(1) + const [isMuted, setIsMuted] = useState(false) + const [progress, setProgress] = useState(0) + const [isLoading, setIsLoading] = useState(false) + const [canPlay, setCanPlay] = useState(false) + const [startPlay, setStartPlay] = useState(false) + const [isMobileView, setIsMobileView] = useState(false) + const [playbackRate, setPlaybackRate] = useState(1) + const [anchorEl, setAnchorEl] = useState(null) + const reDownload = useRef(false) + + const resetVideoState = () => { + // Reset all states to their initial values + setPlaying(false); + setVolume(1); + setMutedVolume(1); + setIsMuted(false); + setProgress(0); + setIsLoading(false); + setCanPlay(false); + setStartPlay(false); + setIsMobileView(false); + setPlaybackRate(1); + setAnchorEl(null); + + // Reset refs to their initial values + if (videoRef.current) { + videoRef.current.pause(); // Ensure the video is paused + videoRef.current.currentTime = 0; // Reset video progress + } + reDownload.current = false; + }; + + const src = useMemo(() => { + if(name && identifier && service){ + return `${node || getBaseApiReact()}/arbitrary/${service}/${name}/${identifier}` + } + return '' + }, [service, name, identifier]) + + useEffect(()=> { + resetVideoState() + }, [keyIdentifier]) + const resourceStatus = useMemo(() => { + return download?.status || {} + }, [download]) + + const minSpeed = 0.25; + const maxSpeed = 4.0; + const speedChange = 0.25; + + const updatePlaybackRate = (newSpeed: number) => { + if (videoRef.current) { + if (newSpeed > maxSpeed || newSpeed < minSpeed) + newSpeed = minSpeed + videoRef.current.playbackRate = newSpeed + setPlaybackRate(newSpeed) + } + } + + const increaseSpeed = (wrapOverflow = true) => { + const changedSpeed = playbackRate + speedChange + let newSpeed = wrapOverflow ? changedSpeed : Math.min(changedSpeed, maxSpeed) + + + if (videoRef.current) { + updatePlaybackRate(newSpeed); + } + } + + const decreaseSpeed = () => { + if (videoRef.current) { + updatePlaybackRate(playbackRate - speedChange); + } + } + + + const togglePlay = async () => { + if (!videoRef.current) return + setStartPlay(true) + if (!src || resourceStatus?.status !== 'READY') { + ReactDOM.flushSync(() => { + setIsLoading(true) + }) + getSrc() + } + if (playing) { + videoRef.current.pause() + } else { + videoRef.current.play() + } + setPlaying(!playing) + } + + + const onVolumeChange = (_: any, value: number | number[]) => { + if (!videoRef.current) return + videoRef.current.volume = value as number + setVolume(value as number) + setIsMuted(false) + } + + const onProgressChange = (_: any, value: number | number[]) => { + if (!videoRef.current) return + videoRef.current.currentTime = value as number + setProgress(value as number) + if (!playing) { + videoRef.current.play() + setPlaying(true) + } + } + + const handleEnded = () => { + setPlaying(false) + } + + const updateProgress = () => { + if (!videoRef.current) return + setProgress(videoRef.current.currentTime) + } + + const [isFullscreen, setIsFullscreen] = useState(false) + + const enterFullscreen = () => { + if (!videoRef.current) return + if (videoRef.current.requestFullscreen) { + videoRef.current.requestFullscreen() + } + } + + const exitFullscreen = () => { + if (document.exitFullscreen) { + document.exitFullscreen() + } + } + + const toggleFullscreen = () => { + isFullscreen ? exitFullscreen() : enterFullscreen() + } + + + useEffect(() => { + const handleFullscreenChange = () => { + setIsFullscreen(!!document.fullscreenElement) + } + + document.addEventListener('fullscreenchange', handleFullscreenChange) + return () => { + document.removeEventListener('fullscreenchange', handleFullscreenChange) + } + }, []) + + + + const handleCanPlay = () => { + setIsLoading(false) + setCanPlay(true) + } + + const getSrc = React.useCallback(async () => { + if (!name || !identifier || !service) return + try { + downloadResource({ + name, + service, + identifier + }) + } catch (error) { + console.error(error) + } + }, [identifier, name, service]) + + + + + function formatTime(seconds: number): string { + seconds = Math.floor(seconds) + let minutes: number | string = Math.floor(seconds / 60) + let hours: number | string = Math.floor(minutes / 60) + + let remainingSeconds: number | string = seconds % 60 + let remainingMinutes: number | string = minutes % 60 + + if (remainingSeconds < 10) { + remainingSeconds = '0' + remainingSeconds + } + + if (remainingMinutes < 10) { + remainingMinutes = '0' + remainingMinutes + } + + if (hours === 0) { + hours = '' + } + else { + hours = hours + ':' + } + + return hours + remainingMinutes + ':' + remainingSeconds + } + + const reloadVideo = () => { + if (!videoRef.current) return + const currentTime = videoRef.current.currentTime + videoRef.current.src = src + videoRef.current.load() + videoRef.current.currentTime = currentTime + if (playing) { + videoRef.current.play() + } + } + + useEffect(() => { + if ( + resourceStatus?.status === 'DOWNLOADED' && + reDownload?.current === false + ) { + getSrc() + reDownload.current = true + } + }, [getSrc, resourceStatus]) + + const handleMenuOpen = (event: any) => { + setAnchorEl(event.currentTarget) + } + + const handleMenuClose = () => { + setAnchorEl(null) + } + + useEffect(() => { + const videoWidth = videoRef?.current?.offsetWidth + if (videoWidth && videoWidth <= 600) { + setIsMobileView(true) + } + }, [canPlay]) + + const getDownloadProgress = (current: number, total: number) => { + const progress = current / total * 100; + return Number.isNaN(progress) ? '' : progress.toFixed(0) + '%' + } + const mute = () => { + setIsMuted(true) + setMutedVolume(volume) + setVolume(0) + if (videoRef.current) videoRef.current.volume = 0 + } + const unMute = () => { + setIsMuted(false) + setVolume(mutedVolume) + if (videoRef.current) videoRef.current.volume = mutedVolume + } + + const toggleMute = () => { + isMuted ? unMute() : mute(); + } + + const changeVolume = (volumeChange: number) => { + if (videoRef.current) { + const minVolume = 0; + const maxVolume = 1; + + + let newVolume = volumeChange + volume + + newVolume = Math.max(newVolume, minVolume) + newVolume = Math.min(newVolume, maxVolume) + + setIsMuted(false) + setMutedVolume(newVolume) + videoRef.current.volume = newVolume + setVolume(newVolume); + } + + } + const setProgressRelative = (secondsChange: number) => { + if (videoRef.current) { + const currentTime = videoRef.current?.currentTime + const minTime = 0 + const maxTime = videoRef.current?.duration || 100 + + let newTime = currentTime + secondsChange; + newTime = Math.max(newTime, minTime) + newTime = Math.min(newTime, maxTime) + videoRef.current.currentTime = newTime; + setProgress(newTime); + } + } + + const setProgressAbsolute = (videoPercent: number) => { + if (videoRef.current) { + videoPercent = Math.min(videoPercent, 100) + videoPercent = Math.max(videoPercent, 0) + const finalTime = videoRef.current?.duration * videoPercent / 100 + videoRef.current.currentTime = finalTime + setProgress(finalTime); + } + } + + + const keyboardShortcutsDown = (e: React.KeyboardEvent) => { + e.preventDefault() + + switch (e.key) { + case Key.Add: increaseSpeed(false); break; + case '+': increaseSpeed(false); break; + case '>': increaseSpeed(false); break; + + case Key.Subtract: decreaseSpeed(); break; + case '-': decreaseSpeed(); break; + case '<': decreaseSpeed(); break; + + case Key.ArrowLeft: { + if (e.shiftKey) setProgressRelative(-300); + else if (e.ctrlKey) setProgressRelative(-60); + else if (e.altKey) setProgressRelative(-10); + else setProgressRelative(-5); + } break; + + case Key.ArrowRight: { + if (e.shiftKey) setProgressRelative(300); + else if (e.ctrlKey) setProgressRelative(60); + else if (e.altKey) setProgressRelative(10); + else setProgressRelative(5); + } break; + + case Key.ArrowDown: changeVolume(-0.05); break; + case Key.ArrowUp: changeVolume(0.05); break; + } + } + + const keyboardShortcutsUp = (e: React.KeyboardEvent) => { + e.preventDefault() + + switch (e.key) { + case ' ': togglePlay(); break; + case 'm': toggleMute(); break; + + case 'f': enterFullscreen(); break; + case Key.Escape: exitFullscreen(); break; + + case '0': setProgressAbsolute(0); break; + case '1': setProgressAbsolute(10); break; + case '2': setProgressAbsolute(20); break; + case '3': setProgressAbsolute(30); break; + case '4': setProgressAbsolute(40); break; + case '5': setProgressAbsolute(50); break; + case '6': setProgressAbsolute(60); break; + case '7': setProgressAbsolute(70); break; + case '8': setProgressAbsolute(80); break; + case '9': setProgressAbsolute(90); break; + } + } + + return ( + + + {isLoading && ( + + + {resourceStatus && ( + + {resourceStatus?.status === 'REFETCHING' ? ( + <> + <> + {getDownloadProgress(resourceStatus?.localChunkCount, resourceStatus?.totalChunkCount)} + + + <> Refetching data in 25 seconds + + ) : resourceStatus?.status === 'DOWNLOADED' ? ( + <>Download Completed: building tutorial video... + ) : resourceStatus?.status !== 'READY' ? ( + <> + {getDownloadProgress(resourceStatus?.localChunkCount, resourceStatus?.totalChunkCount)} + + + ) : ( + <>Fetching tutorial from the Qortal Network... + )} + + )} + + )} + {((!src && !isLoading) || !startPlay) && ( + { + togglePlay() + }} + sx={{ + cursor: 'pointer' + }} + > + + + )} + + + + + {isMobileView && canPlay ? ( + <> + + {playing ? : } + + + + + + + + + + + + + + increaseSpeed()}> + + Speed: {playbackRate}x + + + + + + + + ) : canPlay ? ( + <> + + {playing ? : } + + + + + + + {progress && videoRef.current?.duration && formatTime(progress)}/ + {progress && + videoRef.current?.duration && + formatTime(videoRef.current?.duration)} + + + {isMuted ? : } + + + increaseSpeed()} + > + Speed: {playbackRate}x + + + + + + ) : null} + + + ) +} diff --git a/src/components/Group/Home.tsx b/src/components/Group/Home.tsx index f1af521..4154fc6 100644 --- a/src/components/Group/Home.tsx +++ b/src/components/Group/Home.tsx @@ -1,5 +1,5 @@ -import { Box, Button, Typography } from "@mui/material"; -import React from "react"; +import { Box, Button, ButtonBase, Typography } from "@mui/material"; +import React, { useContext } from "react"; import { Spacer } from "../../common/Spacer"; import { ListOfThreadPostsWatched } from "./ListOfThreadPostsWatched"; import { ThingsToDoInitial } from "./ThingsToDoInitial"; @@ -7,6 +7,9 @@ import { GroupJoinRequests } from "./GroupJoinRequests"; import { GroupInvites } from "./GroupInvites"; import RefreshIcon from "@mui/icons-material/Refresh"; import { ListOfGroupPromotions } from "./ListOfGroupPromotions"; +import HelpIcon from '@mui/icons-material/Help'; +import { useHandleTutorials } from "../Tutorials/useHandleTutorials"; +import { GlobalContext } from "../../App"; export const Home = ({ refreshHomeDataFunc, @@ -22,6 +25,8 @@ export const Home = ({ setOpenAddGroup, setMobileViewMode, }) => { + const { showTutorial } = useContext(GlobalContext); + return ( + + { + + showTutorial('getting-started', true) + + + }} > + + + + { + const { openTutorialModal, setOpenTutorialModal } = useContext(GlobalContext); + const [multiNumber, setMultiNumber] = useState(0) + const handleClose = ()=> { + setOpenTutorialModal(null) + setMultiNumber(0) + } + if(!openTutorialModal) return null + if(openTutorialModal?.multi){ + const selectedTutorial = openTutorialModal?.multi[multiNumber] + return ( + + setMultiNumber(value)} aria-label="basic tabs example"> + {openTutorialModal?.multi?.map((item, index)=> { + return ( + + + ) + })} + + + {selectedTutorial?.title} {` Tutorial`} + + + + + + + + + + ) + } + return ( + <> + + + {openTutorialModal?.title} {` Tutorial`} + + + + + + + + + + + ) +} diff --git a/src/components/Tutorials/useHandleTutorials.tsx b/src/components/Tutorials/useHandleTutorials.tsx new file mode 100644 index 0000000..dc2f19b --- /dev/null +++ b/src/components/Tutorials/useHandleTutorials.tsx @@ -0,0 +1,180 @@ +import React, { useCallback, useEffect, useState } from "react"; +import { saveToLocalStorage } from "../Apps/AppsNavBar"; +import { getData, storeData } from "../../utils/chromeStorage"; + + +const checkIfGatewayIsOnline = async () => { + try { + const url = `https://ext-node.qortal.link/admin/status`; + const response = await fetch(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + const data = await response.json(); + if (data?.height) { + return true + } + return false + + } catch (error) { + return false + + } + } +export const useHandleTutorials = () => { + const [openTutorialModal, setOpenTutorialModal] = useState(null); +const [shownTutorials, setShowTutorials] = useState(null) + + +useEffect(()=> { + const getSavedData = async()=> { + try { + const storedData = await getData("shown-tutorials").catch(() => {}); + + + if (storedData) { + setShowTutorials(storedData); + } else { + setShowTutorials({}) + } + } catch (error) { + //error + } + } + getSavedData() +}, []) + + const saveShowTutorial = useCallback(async (type)=> { + try { + + setShowTutorials((prev)=> { + return { + ...(prev || {}), + [type]: true + } + }) + const storedData = await getData("shown-tutorials").catch(() => {}); + const newData = { + ...storedData, + [type]: true + } + await storeData("shown-tutorials", newData) + } catch (error) { + //error + } + }, []) + const showTutorial = useCallback(async (type, isForce) => { + try { + const isOnline = await checkIfGatewayIsOnline() + if(!isOnline) return + switch (type) { + case "create-account": + { + if((shownTutorials || {})['create-account'] && !isForce) return + saveShowTutorial('create-account') + setOpenTutorialModal({ + title: "Account Creation", + resource: { + name: "a-test", + service: "VIDEO", + identifier: "account-creation-go", + }, + }); + } + break; + case "important-information": + { + if((shownTutorials || {})['important-information'] && !isForce) return + saveShowTutorial('important-information') + + setOpenTutorialModal({ + title: "Important Information!", + resource: { + name: "a-test", + service: "VIDEO", + identifier: "important-information-go", + }, + }); + } + break; + case "getting-started": + { + + if((shownTutorials || {})['getting-started'] && !isForce) return + saveShowTutorial('getting-started') + + setOpenTutorialModal({ + multi: [ + + { + title: "1. Getting Started", + resource: { + name: "a-test", + service: "VIDEO", + identifier: "getting-started-go", + }, + }, + { + title: "2. Overview", + resource: { + name: "a-test", + service: "VIDEO", + identifier: "overview-go", + }, + }, + { + title: "3. Qortal Groups", + resource: { + name: "a-test", + service: "VIDEO", + identifier: "groups-go", + }, + }, + ], + }); + } + break; + case "qapps": + { + if((shownTutorials || {})['qapps'] && !isForce) return + saveShowTutorial('qapps') + + setOpenTutorialModal({ + multi: [ + { + title: "1. Apps Dashboard", + resource: { + name: "a-test", + service: "VIDEO", + identifier: "apps-dashboard-go", + }, + }, + { + title: "2. Apps Navigation", + resource: { + name: "a-test", + service: "VIDEO", + identifier: "apps-navigation-go", + }, + } + + ], + }); + } + break; + default: + break; + } + } catch (error) { + //error + } + }, [shownTutorials]); + return { + showTutorial, + openTutorialModal, + setOpenTutorialModal, + shownTutorialsInitiated: !!shownTutorials + }; +}; diff --git a/src/index.css b/src/index.css index 6ea9a22..86948f5 100644 --- a/src/index.css +++ b/src/index.css @@ -121,4 +121,5 @@ html, body { .swiper { width: 100%; -} \ No newline at end of file +} +