: children;
+ }
+}
+
+export default ErrorHandler;
diff --git a/src/app/components/ErrorHandling/ErrorMsg.tsx b/src/app/components/ErrorHandling/ErrorMsg.tsx
new file mode 100644
index 000000000..7ad1935a2
--- /dev/null
+++ b/src/app/components/ErrorHandling/ErrorMsg.tsx
@@ -0,0 +1,92 @@
+/* eslint-disable react/prop-types */
+import React from 'react';
+
+/*
+This file determines what text will be displayed to the user if any one of the following fail to load:
+
+ 1. if the content script has been launched on the current tab
+ 2. if React Dev Tools has been installed
+ 3. if target tab contains a compatible React app
+
+*/
+
+// parses loadingArray and status and returns the correct message
+function parseError(loadingArray: [], status: Record): string {
+ let stillLoading = true;
+ loadingArray.forEach((e) => {
+ if (e === false) stillLoading = false;
+ });
+
+ if (stillLoading) return 'default'; // As long as everything is still loading dont diplay an error message
+
+ // If we're done loading everything, return the first status that fails
+ if (!status.contentScriptLaunched) return 'Content Script Error';
+ if (!status.reactDevToolsInstalled) return 'RDT Error';
+ if (!status.targetPageisaReactApp) return 'Not React App';
+ return 'default';
+}
+
+function ErrorMsg({ loadingArray, status, launchContent, reinitialize }): JSX.Element {
+ switch (
+ parseError(loadingArray, status) // parseError returns a string based on the loadingArray and status. The returned string is matched to a case so that an appropriate error message will be displayed to the user
+ ) {
+ case 'Content Script Error':
+ return (
+
+ Target App not yet found...
+
+ If you encounter this error on the initial launch of Reactime, try refreshing the webpage
+ you are developing.
+
+
+ If Reactime is running as an iframe in your developer tools, right click on the Reactime
+ application and click 'Reload Frame'
+
+
+ NOTE: By default Reactime only launches the content script on URLS starting with
+ localhost.
+
+ If your target URL does not match, you can manually launch the content script below.
+
+
+
+
+ The Target app is either not a React application or is not compatible with Reactime.
+
+ );
+ default:
+ return null;
+ }
+}
+
+export default ErrorMsg;
diff --git a/src/app/components/ErrorHandling/Loader.tsx b/src/app/components/ErrorHandling/Loader.tsx
new file mode 100644
index 000000000..797a1e231
--- /dev/null
+++ b/src/app/components/ErrorHandling/Loader.tsx
@@ -0,0 +1,32 @@
+// /* eslint-disable react/prop-types */
+
+import React from 'react';
+import { ClipLoader } from 'react-spinners';
+import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
+import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
+
+/*
+This file is what decides what icon (loading, checkmark, exclamation point) is displayed next to the checks in the ErrorContainer loading screen:
+
+ 1. if the content script has been launched on the current tab
+ 2. if React Dev Tools has been installed
+ 3. if target tab contains a compatible React app
+*/
+
+const handleResult = (result: boolean): JSX.Element =>
+ result ? (
+ // if result boolean is true, we display a checkmark icon
+ ) : (
+ // if the result boolean is false, we display a fail icon
+ );
+
+// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
+// Returns the 'Loader' component
+const Loader = ({ loading, result }): JSX.Element =>
+ loading ? (
+ // if the loadingArray value is true, we display a loading icon
+ ) : (
+ handleResult(result) // else we display a component produced by 'handleResult' depending on if the result parameter (which takes an argument from the status object in 'ErrorContainer') is true or false
+ );
+
+export default Loader;
diff --git a/src/app/containers/ErrorContainer.tsx b/src/app/containers/ErrorContainer.tsx
index 046fd3b14..7bd786104 100644
--- a/src/app/containers/ErrorContainer.tsx
+++ b/src/app/containers/ErrorContainer.tsx
@@ -1,24 +1,112 @@
-import React from 'react';
-import { useDispatch, useSelector } from 'react-redux';
+/* eslint-disable max-len */
+import React, { useState, useEffect, useRef } from 'react';
import { launchContentScript } from '../slices/mainSlice';
+import Loader from '../components/ErrorHandling/Loader';
+import ErrorMsg from '../components/ErrorHandling/ErrorMsg';
+import { useDispatch, useSelector } from 'react-redux';
import { MainState, RootState, ErrorContainerProps } from '../FrontendTypes';
+import { current } from '@reduxjs/toolkit';
import { RefreshCw, Github, PlayCircle } from 'lucide-react';
+/*
+This is the loading screen that a user may get when first initalizing the application. This page checks:
+
+ 1. if the content script has been launched on the current tab
+ 2. if React Dev Tools has been installed
+ 3. if target tab contains a compatible React app
+*/
+
function ErrorContainer(props: ErrorContainerProps): JSX.Element {
const dispatch = useDispatch();
const { tabs, currentTitle, currentTab }: MainState = useSelector(
(state: RootState) => state.main,
);
+ const [loadingArray, setLoading] = useState([true, true, true]); // We create a local state "loadingArray" and set it to an array with three true elements. These will be used as hooks for error checking against a 'status' object that is declared later in a few lines. 'loadingArray' is used later in the return statement to display a spinning loader icon if it's true. If it's false, either a checkmark icon or an exclamation icon will be displayed to the user.
+ const titleTracker = useRef(currentTitle); // useRef returns an object with a property 'initialValue' and a value of whatever was passed in. This allows us to reference a value that's not needed for rendering
+ const timeout = useRef(null);
+ const { port } = props;
- // function that launches the main app and refreshes the page
+ // function that launches the main app
function launch(): void {
dispatch(launchContentScript(tabs[currentTab]));
- // Allow the dispatch to complete before refreshing
- setTimeout(() => {
- chrome.tabs.reload(currentTab);
- }, 100);
}
+ function reinitialize(): void {
+ port.postMessage({
+ action: 'reinitialize',
+ tabId: currentTab,
+ });
+ }
+
+ let status = {
+ // We create a status object that we may use later if tabs[currentTab] exists
+ contentScriptLaunched: false,
+ reactDevToolsInstalled: false,
+ targetPageisaReactApp: false,
+ };
+
+ if (tabs[currentTab]) {
+ // If we do have a tabs[currentTab] object, we replace the status obj we declared above with the properties of the tabs[currentTab].status
+ Object.assign(status, tabs[currentTab].status);
+ }
+
+ // hook that sets timer while waiting for a snapshot from the background script, resets if the tab changes/reloads
+ useEffect(() => {
+ // We declare a function
+ function setLoadingArray(i: number, value: boolean) {
+ // 'setLoadingArray' checks an element in our 'loadingArray' local state and compares it with passed in boolean argument. If they don't match, we update our local state replacing the selected element with the boolean argument
+ if (loadingArray[i] !== value) {
+ // this conditional helps us avoid unecessary state changes if the element and the value are already the same
+ const loadingArrayClone = [...loadingArray];
+ loadingArrayClone[i] = value;
+ setLoading(loadingArrayClone);
+ }
+ }
+
+ if (titleTracker.current !== currentTitle) {
+ // if the current tab changes/reloads, we reset loadingArray to it's default [true, true, true]
+ titleTracker.current = currentTitle;
+ setLoadingArray(0, true);
+ setLoadingArray(1, true);
+ setLoadingArray(2, true);
+
+ if (timeout.current) {
+ // if there is a current timeout set, we clear it
+ clearTimeout(timeout.current);
+ timeout.current = null;
+ }
+ }
+
+ if (!status.contentScriptLaunched) {
+ // if content script hasnt been launched/found, set a timer or immediately update 'loadingArray' state
+
+ if (loadingArray[0] === true) {
+ // if loadingArray[0] is true, then that means our timeout.current is still null so we now set it to a setTimeout function that will flip loadingArray[0] to false after 3 seconds
+ timeout.current = setTimeout(() => {
+ setLoadingArray(0, false);
+ }, 3000); // increased from 1500
+ }
+ } else {
+ setLoadingArray(0, false); // if status.contentScriptLaunched is true, that means timeout.current !== null. This means that useEffect was triggered previously.
+ }
+
+ // The next two if statements are written in a way to allow the checking of 'content script hook', 'reactDevTools check', and 'target page is a react app' to be run in chronological order.
+ if (loadingArray[0] === false && status.contentScriptLaunched === true) {
+ timeout.current = setTimeout(() => {
+ setLoadingArray(1, false);
+ }, 3000); // increased from 1500
+ setLoadingArray(1, false);
+ }
+ if (loadingArray[1] === false && status.reactDevToolsInstalled === true) {
+ setLoadingArray(2, false);
+ }
+
+ // Unload async function when Error Container is unmounted
+ return () => {
+ clearTimeout(timeout.current);
+ };
+ }, [status, currentTitle, timeout, loadingArray]); // within our dependency array, we're keeping track of if the status, currentTitle/tab, timeout, or loadingArray changes and we re-run the useEffect hook if they do
+
return (
@@ -30,10 +118,21 @@ function ErrorContainer(props: ErrorContainerProps): JSX.Element {
Welcome to Reactime
+
+
Checking if content script has been launched on current tab
+
+
+
Checking if React Dev Tools has been installed
+
+
+
Checking if target is a compatible React app
+
+
+
- To ensure Reactime works correctly with your React application, please either refresh
- your development page or click the launch button below. This allows Reactime to properly
- connect with your app and start monitoring state changes.
+ To ensure Reactime works correctly with your React application, please refresh your
+ development page. This allows Reactime to properly connect with your app and start
+ monitoring state changes.
Important: Reactime requires React Developer Tools to be installed. If you haven't
@@ -55,11 +154,6 @@ function ErrorContainer(props: ErrorContainerProps): JSX.Element {
starting with localhost.