@fortawesome/free-solid-svg-icons#faTimesCircle TypeScript Examples

The following examples show how to use @fortawesome/free-solid-svg-icons#faTimesCircle. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: CustomToast.tsx    From devex with GNU General Public License v3.0 6 votes vote down vote up
CustomToast: React.FC<IProps> = (props) => {

  const { setShowToast, isSuccess, bodyText } = props

  return <div
    aria-live="polite"
    aria-atomic="true"
    style={{
      position: 'fixed',
      top: '90px',
      right: '20px',
    }}
  >
    <Toast
      onClose={() => setShowToast(false)}
      show={true}
      delay={3000}
      autohide
      className={isSuccess ? 'custom-toast-success' : 'custom-toast-failure'}
    >
      <Toast.Body className='d-flex' style={{ padding: '0.35rem' }}>
        <span className='mr-2'>
          {
            isSuccess
              ? <FontAwesomeIcon size='lg' className='custom-toast-icon' icon={faCheck} />
              : <FontAwesomeIcon size='lg' className='custom-toast-icon' icon={faTimesCircle} />
          }
        </span>
        <span className='custom-toast-text'>{bodyText}</span>
      </Toast.Body>
    </Toast>
  </div>
}
Example #2
Source File: Popup.tsx    From zwo-editor with MIT License 6 votes vote down vote up
Popop = (props: { width: string, height?: string, dismiss: Function, children: any}) => {

  return (
    <div className='popup-background'>
      <div className='popup' style={{width: props.width, height: props.height}}>        
        <button className="close" onClick={()=>props.dismiss()}>
          <FontAwesomeIcon icon={faTimesCircle} size="lg" fixedWidth />
        </button>
        {props.children}
      </div>
    </div>
  )
}
Example #3
Source File: Scoreboard.tsx    From longwave with MIT License 6 votes vote down vote up
function PlayerRow(props: { playerId: string }) {
  const { gameState, setGameState } = useContext(GameModelContext);
  const player = gameState.players[props.playerId];
  const [hovered, setHovered] = useState(false);

  const iconContainerStyle = {
    marginLeft: 4,
    width: 20,
  };

  return (
    <div
      style={{ marginLeft: 16, display: "flex", flexFlow: "row" }}
      onMouseEnter={() => setHovered(true)}
      onMouseLeave={() => setHovered(false)}
    >
      {player.name}
      {hovered ? (
        <div
          style={{
            ...iconContainerStyle,
            cursor: "pointer",
          }}
          onClick={() => {
            delete gameState.players[props.playerId];
            setGameState(gameState);
          }}
        >
          <FontAwesomeIcon icon={faTimesCircle} />
        </div>
      ) : (
        <div style={iconContainerStyle} />
      )}
    </div>
  );
}
Example #4
Source File: App.tsx    From TabMerger with GNU General Public License v3.0 6 votes vote down vote up
/**
 * Icons listed here no longer need to be imported in other files.
 * Instead a string can be passed to the `icon` property.
 * By default, the icons are all "solid", which is why `far` is also added ...
 * ... simply do `icon={["far", "icon-name"]}` to use the "regular" version of "icon-name"
 * @see https://fontawesome.com/v5.15/how-to-use/on-the-web/using-with/react#using
 */
library.add(
  far,
  faCog,
  faSearch,
  faTimes,
  faWindowRestore,
  faEllipsisV,
  faWindowMaximize,
  faTimesCircle,
  faStar,
  faMask,
  faExclamationCircle,
  faCopy,
  faAngleDown,
  faAngleUp,
  faCheckCircle,
  faUpload
);
Example #5
Source File: DeploySiteConfig.tsx    From argo-react with MIT License 4 votes vote down vote up
function DeploySiteConfig() {
  const history = useHistory();

  const {
    user,
    selectedOrg,
    selectedRepoForTriggerDeployment,
    orgLoading,
    userLoading,
  } = useContext<IStateModel>(StateContext);
  const { setLatestDeploymentConfig, setSelectedOrganization } =
    useContext<IActionModel>(ActionContext);

  const [createDeployProgress, setCreateDeployProgress] = useState(1);
  const [showRepoOrgDropdown, setShowRepoOrgDropdown] = useState<boolean>(false);
  const [reposOwnerDetails, setReposOwnerDetails] = useState<any[]>([]);
  const [reposSelectedOwnerRepoDetails, setReposSelectedOwnerRepoDetails] = useState<
    any[]
  >([]);
  const [selectedRepoOwner, setSelectedRepoOwner] = useState<any>();
  const [currentRepoOwner, setCurrentRepoOwner] = useState<string>("");
  const [ownerLoading, setOwnerLoading] = useState<boolean>(true);
  const [repoLoading, setRepoLoading] = useState<boolean>(true);
  const [repoBranches, setRepoBranches] = useState<any[]>([]);
  const [buildEnv, setBuildEnv] = useState<any[]>([]);
  const [repoBranchesLoading, setRepoBranchesLoading] = useState<boolean>(true);

  const [autoPublish, setAutoPublish] = useState<boolean>(true);
  const [selectedRepo, setSelectedRepo] = useState<any>();
  const [owner, setOwner] = useState<any>();
  const [branch, setBranch] = useState<string>("master");
  const [workspace, setWorkspace] = useState<string>();
  const [framework, setFramework] = useState<string>("react");
  const [packageManager, setPackageManager] = useState<string>("npm");
  const [buildCommand, setBuildCommand] = useState<string>("");
  const [publishDirectory, setPublishDirectory] = useState<string>("");
  const [protocol, setProtocol] = useState<string>("");
  const [startDeploymentLoading, setStartDeploymentLoading] =
    useState<boolean>(false);
  const [deployDisabled, setDeployDisabled] = useState<boolean>(false);
  const [showGithubRepos, setShowGithubRepos] = useState<boolean>(false);
  const [errorWarning, setErrorWarning] = useState<boolean>(false);
  const [errorMessage, setErrorMessage] = useState<string>("");

  const componentIsMounted = useRef(true);

  useEffect(() => {
    return () => {
      componentIsMounted.current = false;
    };
  }, []);

  useEffect(() => {
    if (
      selectedRepo &&
      owner &&
      branch &&
      framework !== "static" &&
      packageManager &&
      buildCommand &&
      publishDirectory &&
      protocol &&
      selectedOrg?.wallet &&
      !orgLoading
    ) {
      setDeployDisabled(false);
    } else {
      if (
        selectedRepo &&
        owner &&
        branch &&
        framework === "static" &&
        protocol &&
        selectedOrg?.wallet &&
        !orgLoading
      ) {
        setDeployDisabled(false);
      } else {
        setDeployDisabled(true);
      }
    }
  }, [
    selectedRepo,
    owner,
    branch,
    framework,
    packageManager,
    buildCommand,
    publishDirectory,
    user,
    selectedOrg,
    orgLoading,
    protocol,
  ]);

  useEffect(() => {
    if (framework === "static") {
      setPackageManager("");
      setBuildCommand("");
      setPublishDirectory("");
    } else if (framework === "react") {
      setPackageManager("npm");
      setBuildCommand("build");
      setPublishDirectory("build");
    } else if (framework === "vue") {
      setPackageManager("npm");
      setBuildCommand("build");
      setPublishDirectory("dist");
    } else if (framework === "angular") {
      setPackageManager("npm");
      setBuildCommand("build");
      setPublishDirectory("dist/your-app-name");
    } else if (framework === "next") {
      setPackageManager("yarn");
      setBuildCommand("next build && next export");
      setPublishDirectory("out");
    }
  }, [framework]);

  useEffect(() => {
    if (selectedOrg) {
      setOwner(selectedOrg);
    } else if (user?.organizations && user.organizations[0]) {
      setOwner(user.organizations[0]);
    }
  }, [user, selectedOrg]);

  useEffect(() => {
    if (selectedRepoForTriggerDeployment) {
      const repoName = selectedRepoForTriggerDeployment.github_url
        .substring(19, selectedRepoForTriggerDeployment.github_url.length - 4)
        .split("/")[1];
      const ownerName = selectedRepoForTriggerDeployment.github_url
        .substring(19, selectedRepoForTriggerDeployment.github_url.length - 4)
        .split("/")[0];

      setSelectedRepo({
        name: repoName,
        clone_url: selectedRepoForTriggerDeployment.github_url,
      });
      setCurrentRepoOwner(ownerName);
      setFramework(selectedRepoForTriggerDeployment.framework);
      setWorkspace(selectedRepoForTriggerDeployment.workspace);
      setPackageManager(selectedRepoForTriggerDeployment.package_manager);
      setBuildCommand(selectedRepoForTriggerDeployment.build_command);
      setPublishDirectory(selectedRepoForTriggerDeployment.publish_dir);
      setProtocol(selectedRepoForTriggerDeployment.protocol);
      setCreateDeployProgress(3);

      const branchUrl = `https://api.github.com/repos/${ownerName}/${repoName}/branches`;
      ApiService.getGithubRepoBranches(branchUrl).subscribe((res) => {
        if (componentIsMounted.current) {
          setRepoBranches(res.branches);
          setBranch(selectedRepoForTriggerDeployment.branch);
          setRepoBranchesLoading(false);
        }
      });
    }
  }, [selectedRepoForTriggerDeployment]);

  useEffect(() => {
    if (currentRepoOwner && selectedRepoForTriggerDeployment) {
      ApiService.getAllGithubAppInstallation().subscribe((res) => {
        if (componentIsMounted.current) {
          const repoOwners: any[] = res.installations.map((installation: any) => ({
            name: installation.account.login,
            avatar: installation.account.avatar_url,
            installationId: installation.id,
          }));
          if (repoOwners.length) {
            const newRepoOwner = repoOwners.filter(
              (repoOwner) => repoOwner.name === currentRepoOwner,
            )[0];
            setSelectedRepoOwner(newRepoOwner);
          }
        }
      });
    }
  }, [currentRepoOwner, selectedRepoForTriggerDeployment]);

  useEffect(() => {
    const bc = new BroadcastChannel("github_app_auth");
    bc.onmessage = (msg: string) => {
      if (msg === "authorized") {
        setShowGithubRepos(true);
        getAllGithubInstallations();
      }
    };
    return () => {
      bc.close();
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const getAllGithubInstallations = () => {
    setOwnerLoading(true);
    setRepoLoading(true);
    ApiService.getAllGithubAppInstallation().subscribe((res) => {
      if (componentIsMounted.current) {
        const repoOwners: any[] = res.installations.map((installation: any) => ({
          name: installation.account.login,
          avatar: installation.account.avatar_url,
          installationId: installation.id,
        }));
        setReposOwnerDetails(repoOwners);
        if (repoOwners.length) {
          let newRepoOwner = null;
          if (selectedRepoOwner) {
            newRepoOwner = repoOwners.filter(
              (repoOwner) => repoOwner.name === selectedRepoOwner.name,
            )[0];
          } else {
            newRepoOwner = repoOwners[0];
          }
          setSelectedRepoOwner(newRepoOwner);
          setOwnerLoading(false);
          getOwnerRepos(newRepoOwner.installationId);
        } else {
          setOwnerLoading(false);
        }
      }
    });
  };

  const getOwnerRepos = (installationId: string) => {
    setRepoLoading(true);
    ApiService.getAllOwnerRepos(installationId).subscribe((res) => {
      if (componentIsMounted.current) {
        const repositories: any[] = res.repositories.map((repo: any) => ({
          clone_url: repo.clone_url,
          branches_url: repo.branches_url.split("{")[0],
          name: repo.name,
          fullName: repo.full_name,
          private: repo.private,
          repositoryId: repo.id,
        }));
        setReposSelectedOwnerRepoDetails(repositories);
        setRepoLoading(false);
      }
    });
  };

  const selectRepoOwner = (repoOwner: any) => {
    getOwnerRepos(repoOwner.installationId);
    setSelectedRepoOwner(repoOwner);
    setShowRepoOrgDropdown(false);
  };

  const selectRepositories = (repo: any) => {
    setSelectedRepo(repo);
    setCreateDeployProgress(2);
    setRepoBranchesLoading(true);
    ApiService.getGithubRepoBranches(repo.branches_url).subscribe((res) => {
      if (componentIsMounted.current) {
        setRepoBranches(res.branches);
        setBranch(res.branches[0].name);
        setRepoBranchesLoading(false);
      }
    });
  };

  const startDeployment = async () => {
    setErrorWarning(false);
    setErrorMessage("");
    setStartDeploymentLoading(true);
    const configuration = {
      framework,
      workspace,
      packageManager,
      buildCommand,
      publishDir: publishDirectory,
      branch,
      protocol,
    };
    ApiService.createConfiguration(configuration).subscribe(
      (result) => {
        if (componentIsMounted.current) {
          const uniqueTopicId = uuidv4();

          const deployment = {
            orgId: selectedOrg?._id,
            githubUrl: selectedRepo.clone_url,
            folderName: selectedRepo.name,
            owner: selectedRepoOwner.name,
            installationId: selectedRepoOwner.installationId,
            repositoryId: selectedRepo.repositoryId,
            organizationId: owner._id,
            uniqueTopicId,
            configurationId: result._id,
            env: mapBuildEnv(buildEnv),
            createDefaultWebhook: autoPublish,
          };

          ApiService.startDeployment(deployment).subscribe(
            (result) => {
              if (result.success) {
                if (componentIsMounted.current) {
                  setLatestDeploymentConfig(deployment);
                  setStartDeploymentLoading(false);
                  history.push(
                    `/org/${selectedOrg?._id}/sites/${result.projectId}/deployments/${result.deploymentId}`,
                  );
                }
              } else {
                setErrorMessage(result.message);
                setErrorWarning(true);
                setTimeout(() => {
                  setErrorWarning(false);
                  setErrorMessage("");
                }, 5000);
                setStartDeploymentLoading(false);
              }
            },
            (error) => {
              setErrorMessage(error.message);
              setErrorWarning(true);
              setTimeout(() => {
                setErrorWarning(false);
                setErrorMessage("");
              }, 5000);
              setStartDeploymentLoading(false);
            },
          );
        }
      },
      (error) => {
        setErrorMessage(error.message);
        setErrorWarning(true);
        setTimeout(() => {
          setErrorWarning(false);
          setErrorMessage("");
        }, 5000);
        setStartDeploymentLoading(false);
      },
    );
  };

  const mapBuildEnv = (buildEnv: any[]): any => {
    const buildEnvObj = {};
    buildEnv.forEach((env) => {
      Object.assign(buildEnvObj, { [env.key]: env.value });
    });
    return buildEnvObj;
  };

  const openGithubAppAuth = async () => {
    const githubSignInUrl = `${window.location.origin}/#/github/app/${user?._id}`;
    window.open(githubSignInUrl, "_blank");
  };

  const goBackAction = () => {
    if (createDeployProgress === 1) {
      history.goBack();
    } else if (createDeployProgress === 2) {
      setCreateDeployProgress(1);
    } else {
      setCreateDeployProgress(2);
    }
  };

  let buildCommandPrefix: string = "";
  if (packageManager === "npm") {
    buildCommandPrefix = "npm run";
  } else {
    buildCommandPrefix = "yarn";
  }

  const selectProtocol = (selectedProtocol: string) => {
    setProtocol(selectedProtocol);
    setCreateDeployProgress(3);
  };

  const addBuildEnv = () => {
    setBuildEnv([...buildEnv, { key: "", value: "" }]);
  };

  const removeBuildEnvItem = (id: number) => {
    setBuildEnv(buildEnv.filter((item, i) => i !== id));
  };

  const fillEnvKey = (value: string, id: number) => {
    setBuildEnv(
      buildEnv.map((item, i) =>
        i === id ? { key: value, value: item.value } : item,
      ),
    );
  };

  const fillEnvValue = (value: string, id: number) => {
    setBuildEnv(
      buildEnv.map((item, i) => (i === id ? { key: item.key, value } : item)),
    );
  };

  return (
    <div className="DeploySiteConfig">
      <RootHeader parent={"DeploySiteConfig"} />
      <main className="app-main">
        <div className="deploy-site-container">
          <div className="deploy-site-card">
            <div className="deploy-site-card-inner">
              <div className="go-back" onClick={goBackAction}>
                <span>
                  <FontAwesomeIcon icon={faArrowLeft} />
                </span>
                <span>Back</span>
              </div>
              <h1 className="deploy-site-title">Create a new site</h1>
              <div className="deploy-site-subtitle">
                Just follow these 2 step to deploy your website to ArGo
              </div>
              <div className="deploy-site-progress-bar">
                <div className="deploy-site-progress-number-container">
                  {createDeployProgress <= 1 ? (
                    <div
                      className={`deploy-site-progress-number ${
                        createDeployProgress === 1 ? "active" : ""
                      }`}
                    >
                      1
                    </div>
                  ) : (
                    <div className="deploy-site-progress-done">
                      <FontAwesomeIcon icon={faCheck} />
                    </div>
                  )}
                  <div
                    className={`deploy-site-progress-text ${
                      createDeployProgress === 1
                        ? "deploy-site-progress-text-active"
                        : ""
                    }`}
                  >
                    Pick a repository
                  </div>
                </div>
                <div className="deploy-site-progress-number-container">
                  {createDeployProgress <= 2 ? (
                    <div
                      className={`deploy-site-progress-number ${
                        createDeployProgress === 2 ? "active" : ""
                      }`}
                    >
                      2
                    </div>
                  ) : (
                    <div className="deploy-site-progress-done">
                      <FontAwesomeIcon icon={faCheck} />
                    </div>
                  )}
                  <div
                    className={`deploy-site-progress-text ${
                      createDeployProgress === 2
                        ? "deploy-site-progress-text-active"
                        : ""
                    }`}
                  >
                    Pick a Protocol
                  </div>
                </div>
                <div className="deploy-site-progress-number-container">
                  {createDeployProgress <= 3 ? (
                    <div
                      className={`deploy-site-progress-number ${
                        createDeployProgress === 3 ? "active" : ""
                      }`}
                    >
                      3
                    </div>
                  ) : (
                    <div className="deploy-site-progress-done">
                      <FontAwesomeIcon icon={faCheck} />
                    </div>
                  )}
                  <div
                    className={`deploy-site-progress-text ${
                      createDeployProgress === 3
                        ? "deploy-site-progress-text-active"
                        : ""
                    }`}
                  >
                    Build options, and deploy!
                  </div>
                </div>
              </div>
              <div className="deploy-site-form-container">
                {createDeployProgress === 1 && (
                  <div className="deploy-site-form-item">
                    <label className="deploy-site-item-title">
                      {/* Continuous Deployment: GitHub Webhook */}
                      Choose repository
                    </label>
                    <label className="deploy-site-item-subtitle">
                      Choose the repository you want to link to your site on ArGo.
                    </label>
                    {!showGithubRepos ? (
                      <div className="deployment-provider-container">
                        <div className="deployment-provider-title">
                          Connect with your favorite provider
                        </div>
                        <div className="deployment-provider-buttons">
                          <button
                            className="github-button"
                            disabled={userLoading}
                            onClick={openGithubAppAuth}
                          >
                            <span className="github-icon">
                              <GithubIcon />
                            </span>
                            <span>Github</span>
                          </button>
                        </div>
                      </div>
                    ) : reposOwnerDetails.length || ownerLoading ? (
                      <div className="deploy-site-item-repo-list-container">
                        <div className="deploy-site-item-repo-header">
                          <div
                            className="deploy-site-item-repo-header-left"
                            onClick={(e) =>
                              !ownerLoading ? setShowRepoOrgDropdown(true) : null
                            }
                          >
                            {!ownerLoading ? (
                              <LazyLoadedImage height={32} once>
                                <img
                                  src={selectedRepoOwner.avatar}
                                  alt="camera"
                                  className="deploy-site-item-repo-org-avatar"
                                  height={32}
                                  width={32}
                                  loading="lazy"
                                />
                              </LazyLoadedImage>
                            ) : (
                              <Skeleton
                                circle={true}
                                height={32}
                                width={32}
                                duration={2}
                              />
                            )}
                            <span className="deploy-site-item-repo-org-name">
                              {!ownerLoading ? (
                                selectedRepoOwner.name
                              ) : (
                                <Skeleton width={140} height={24} duration={2} />
                              )}
                            </span>
                            <span className="deploy-site-item-repo-down">
                              <FontAwesomeIcon
                                icon={
                                  showRepoOrgDropdown ? faChevronUp : faChevronDown
                                }
                              />
                            </span>
                          </div>
                          <div className="deploy-site-item-repo-header-right">
                            {/* <div className="deploy-site-item-repo-search-container">
                              <span className="deploy-site-item-repo-search-icon">
                                <FontAwesomeIcon icon={faSearch}></FontAwesomeIcon>
                              </span>
                              <input
                                type="text"
                                className="deploy-site-item-repo-search-input"
                                placeholder="Search repos"
                              />
                            </div> */}
                            <div
                              className="refresh-control"
                              onClick={getAllGithubInstallations}
                            >
                              <FontAwesomeIcon icon={faSyncAlt}></FontAwesomeIcon>
                            </div>
                          </div>
                          {showRepoOrgDropdown && (
                            <MemoRepoOrgDropdown
                              setShowDropdown={setShowRepoOrgDropdown}
                              repoOwner={reposOwnerDetails}
                              selectedRepoOwner={selectedRepoOwner}
                              setSelectedRepoOwner={selectRepoOwner}
                            />
                          )}
                        </div>
                        <div className="deploy-site-item-repo-body">
                          {!repoLoading ? (
                            reposSelectedOwnerRepoDetails.map(
                              (repo: any, index: number) => (
                                <MemoRepoItem
                                  skeleton={false}
                                  name={repo.fullName}
                                  privateRepo={repo.private}
                                  key={index}
                                  onClick={() => selectRepositories(repo)}
                                />
                              ),
                            )
                          ) : (
                            <>
                              <MemoRepoItem
                                skeleton={true}
                                name={""}
                                privateRepo={false}
                                onClick={() => null}
                              />
                              <MemoRepoItem
                                skeleton={true}
                                name={""}
                                privateRepo={false}
                                onClick={() => null}
                              />
                            </>
                          )}
                        </div>
                        <div className="deploy-site-item-repo-body">
                          Can’t see your repo here?
                          <a
                            href={`${config.urls.API_URL}/auth/github/app/new`}
                            // eslint-disable-next-line react/jsx-no-target-blank
                            target="_blank"
                            rel="noopener noreferrer"
                          >
                            Configure the ArGo app on GitHub.
                          </a>
                        </div>
                      </div>
                    ) : (
                      <div className="deployment-provider-container">
                        <div className="deployment-provider-title">
                          You don't have any configured owner, Configure it now to
                          view your repositories
                        </div>
                        <div className="deployment-provider-buttons">
                          <button
                            className="github-button"
                            onClick={openGithubAppAuth}
                          >
                            <span className="github-icon">
                              <GithubIcon />
                            </span>
                            <span>Github</span>
                          </button>
                        </div>
                      </div>
                    )}
                  </div>
                )}
                {createDeployProgress === 2 && (
                  <>
                    <div className="deploy-site-form-item">
                      <label className="deploy-site-item-title">
                        Select the protocol to deploy {selectedRepo.name}
                      </label>
                      <label className="deploy-site-item-subtitle">
                        Click on the protocol in which you want ArGo to deploy your
                        site.
                      </label>
                      <div className="deploy-protocol-list-container">
                        <ul className="deploy-protocol-list">
                          <div
                            className="deploy-protocol-image"
                            onClick={(e) => selectProtocol("arweave")}
                          >
                            <LazyLoadedImage height={50} once>
                              <img
                                src={require("../../assets/png/arweave_logo.png")}
                                alt="Arweave"
                                className="deploy-protocol-item-avatar"
                                height={50}
                                width={200}
                                loading="lazy"
                              />
                            </LazyLoadedImage>
                          </div>
                          <div
                            className="deploy-protocol-image"
                            onClick={(e) => selectProtocol("skynet")}
                          >
                            <LazyLoadedImage height={50} once>
                              <img
                                src={require("../../assets/png/skynet_logo.png")}
                                alt="Skynet"
                                className="deploy-protocol-item-avatar"
                                height={50}
                                width={200}
                                loading="lazy"
                              />
                            </LazyLoadedImage>
                            <div className="new-protocol-tag">New</div>
                          </div>
                          <div
                            className="deploy-protocol-image"
                            onClick={(e) => selectProtocol("ipfs-filecoin")}
                          >
                            <LazyLoadedImage height={50} once>
                              <img
                                src={require("../../assets/png/filecoin-full.png")}
                                alt="filecoin"
                                className="deploy-protocol-item-avatar"
                                height={50}
                                width={200}
                                loading="lazy"
                              />
                            </LazyLoadedImage>
                            <div className="new-protocol-tag">New</div>
                          </div>
                          <div
                            className="deploy-protocol-image"
                            onClick={(e) => selectProtocol("ipfs-pinata")}
                          >
                            <LazyLoadedImage height={50} once>
                              <img
                                src={require("../../assets/svg/pinata-full.svg")}
                                alt="filecoin"
                                className="deploy-protocol-item-avatar"
                                height={62}
                                width={220}
                                loading="lazy"
                              />
                            </LazyLoadedImage>
                            <div className="new-protocol-tag">New</div>
                          </div>
                          {/* <div
                            className="deploy-protocol-image"
                            onClick={(e) => selectProtocol("neofs")}
                          >
                            <LazyLoadedImage height={50} once>
                              <img
                                src={require("../../assets/svg/neofs_logo.svg")}
                                alt="neoFS"
                                className="deploy-protocol-item-avatar"
                                height={50}
                                width={200}
                                loading="lazy"
                              />
                            </LazyLoadedImage>
                            <div className="new-protocol-tag">New</div>
                          </div> */}
                        </ul>
                      </div>
                    </div>
                    <div className="button-container">
                      <button
                        type="button"
                        className="cancel-button"
                        onClick={(e) => setCreateDeployProgress(1)}
                      >
                        Back
                      </button>
                    </div>
                  </>
                )}
                {createDeployProgress === 3 && (
                  <>
                    <ReactTooltip />
                    <div className="deploy-site-form-item">
                      <label className="deploy-site-item-title">
                        Deploy settings for {selectedRepo.name}
                      </label>
                      <label className="deploy-site-item-subtitle">
                        Get more control over how ArGo builds and deploys your site
                        with these settings.
                      </label>
                      <div className="deploy-site-item-form">
                        <div className="deploy-site-item-form-item">
                          <label>Owner</label>
                          <div className="deploy-site-item-select-container">
                            <select
                              className="deploy-site-item-select"
                              value={owner._id}
                              onChange={(e) => {
                                const selOrg = user
                                  ? user.organizations
                                    ? user.organizations.filter(
                                        (org) => org._id === e.target.value,
                                      )[0]
                                    : null
                                  : null;
                                setSelectedOrganization(selOrg as any);
                                setOwner(e.target.value);
                              }}
                            >
                              {user?.organizations &&
                                user?.organizations.map((organization, index) => (
                                  <option value={organization._id} key={index}>
                                    {organization.profile.name}
                                  </option>
                                ))}
                            </select>
                            <span className="select-down-icon">
                              <FontAwesomeIcon icon={faChevronDown} />
                            </span>
                          </div>
                        </div>
                        <div className="deploy-site-item-form-item">
                          <label>Branch to deploy</label>
                          <div className="deploy-site-item-select-container">
                            <select
                              className="deploy-site-item-select"
                              value={branch}
                              onChange={(e) => setBranch(e.target.value)}
                            >
                              {repoBranches.map((branch, index) => (
                                <option value={branch.name} key={index}>
                                  {branch.name}
                                </option>
                              ))}
                            </select>
                            <span className="select-down-icon">
                              {!repoBranchesLoading ? (
                                <FontAwesomeIcon icon={faChevronDown} />
                              ) : (
                                <BounceLoader
                                  size={20}
                                  color={"#0a3669"}
                                  loading={true}
                                />
                              )}
                            </span>
                          </div>
                        </div>
                        <div className="deploy-site-item-form-item">
                          <label>
                            Workspace to deploy
                            <span
                              className="tooltip"
                              data-tip="If your app is a monorepo, then you can specify your app directory you want to deploy using the workspace."
                            >
                              <FontAwesomeIcon size="sm" icon={faInfoCircle} />
                            </span>
                          </label>
                          <input
                            type="text"
                            className="deploy-site-item-input"
                            value={workspace}
                            onChange={(e) => setWorkspace(e.target.value)}
                          />
                        </div>
                      </div>
                    </div>
                    <div className="deploy-site-form-item">
                      <label className="deploy-site-item-title">
                        Basic build settings
                      </label>
                      <label className="deploy-site-item-subtitle">
                        If you’re using a static site generator or build tool, we’ll
                        need these settings to build your site.
                      </label>
                      <div className="deploy-site-item-form">
                        <div className="deploy-site-item-form-item">
                          <label>
                            Framework
                            <span
                              className="tooltip"
                              data-tip="The framework that your app is built upon."
                            >
                              <FontAwesomeIcon size="sm" icon={faInfoCircle} />
                            </span>
                          </label>
                          <div className="deploy-site-item-select-container">
                            <select
                              className="deploy-site-item-select"
                              value={framework}
                              onChange={(e) => setFramework(e.target.value)}
                            >
                              <option value="static">
                                No Framework - Simple JavaScript App
                              </option>
                              <option value="react">Create React App</option>
                              <option value="vue">Vue App</option>
                              <option value="angular">Angular App</option>
                              {protocol !== "skynet" && (
                                <option value="next">Next.js App</option>
                              )}
                            </select>
                            <span className="select-down-icon">
                              <FontAwesomeIcon icon={faChevronDown} />
                            </span>
                          </div>
                        </div>
                        {framework !== "static" && (
                          <>
                            <div className="deploy-site-item-form-item">
                              <label>
                                Package Manager
                                <span
                                  className="tooltip"
                                  data-tip="The package manager that you want your app to be built with."
                                >
                                  <FontAwesomeIcon size="sm" icon={faInfoCircle} />
                                </span>
                              </label>
                              <div className="deploy-site-item-select-container">
                                <select
                                  className="deploy-site-item-select"
                                  value={packageManager}
                                  onChange={(e) => setPackageManager(e.target.value)}
                                >
                                  <option value="npm">NPM</option>
                                  <option value="yarn">YARN</option>
                                </select>
                                <span className="select-down-icon">
                                  <FontAwesomeIcon icon={faChevronDown} />
                                </span>
                              </div>
                            </div>
                            <div className="deploy-site-item-form-item">
                              <label>
                                Build command
                                <span
                                  className="tooltip"
                                  data-tip="The command your frontend framework provides for compiling your code."
                                >
                                  <FontAwesomeIcon size="sm" icon={faInfoCircle} />
                                </span>
                              </label>
                              {framework !== "next" ? (
                                <div className="deploy-site-item-input-container">
                                  <input
                                    type="text"
                                    className="deploy-site-item-input-disabled"
                                    value={buildCommandPrefix}
                                    disabled
                                  />
                                  <input
                                    type="text"
                                    className="deploy-site-item-input-build"
                                    value={buildCommand}
                                    onChange={(e) => setBuildCommand(e.target.value)}
                                  />
                                </div>
                              ) : (
                                <input
                                  type="text"
                                  className="deploy-site-item-input"
                                  value={buildCommand}
                                  onChange={(e) => setBuildCommand(e.target.value)}
                                />
                              )}
                            </div>
                            <div className="deploy-site-item-form-item">
                              <label>
                                Publish directory
                                <span
                                  className="tooltip"
                                  data-tip="The directory in which your compiled frontend will be located."
                                >
                                  <FontAwesomeIcon size="sm" icon={faInfoCircle} />
                                </span>
                              </label>
                              <input
                                type="text"
                                className="deploy-site-item-input"
                                value={publishDirectory}
                                onChange={(e) => setPublishDirectory(e.target.value)}
                              />
                            </div>
                          </>
                        )}
                      </div>
                    </div>
                    <div className="deploy-site-form-item">
                      <label className="deploy-site-item-title">
                        Advanced build settings
                      </label>
                      <label className="deploy-site-item-subtitle">
                        Define environment variables for more control and flexibility
                        over your build.
                      </label>

                      <div className="deploy-site-item-form">
                        <div className="deploy-site-item-form-item">
                          <label>
                            Continuous Deployment{" "}
                            <span className="new-item-tag">NEW</span>
                          </label>
                          <label className="deploy-site-item-subtitle">
                            Enabling this will automatically create a production CD
                            pipeline for your selected branch. When you push any new
                            code to GitHub, we will run our build tool and deploy the
                            result.
                          </label>
                        </div>
                        <div className="webhook-confirm-container">
                          <span className="confirm-checkbox">
                            <input
                              type="checkbox"
                              checked={autoPublish}
                              onChange={(e) => setAutoPublish(e.target.checked)}
                            />
                          </span>
                          <span>
                            <div className="webhook-title">
                              Do you want to enable Continuous Deployment?
                            </div>
                            <div className="webhook-note">
                              Note: If the project already has CD enabled, this won't
                              overwrite the existing configuration. To change this,
                              you have to go to Project Settings.
                            </div>
                          </span>
                        </div>
                      </div>
                      <div className="deploy-site-item-form">
                        <div className="deploy-site-item-form-item">
                          <label>Environment Variables</label>
                          <label className="deploy-site-item-subtitle">
                            Note that adding environment variables here won't work if
                            project already exists, you have to add environment
                            variables by going to your Project Settings {"->"}{" "}
                            Environment Variables
                          </label>
                        </div>
                        {buildEnv.length !== 0 && (
                          <div className="deploy-site-item-form-item">
                            <div className="deploy-site-env-title">
                              <label className="deploy-site-env-title-item">
                                Key
                              </label>
                              <label className="deploy-site-env-title-item">
                                Value
                              </label>
                            </div>
                            {buildEnv.map((env, i) => (
                              <div
                                className="deploy-site-item-env-container"
                                key={i}
                              >
                                <input
                                  type="text"
                                  className="deploy-site-env-input"
                                  placeholder="VARIABLE_NAME"
                                  value={env.key}
                                  onChange={(e) => fillEnvKey(e.target.value, i)}
                                />
                                <input
                                  type="text"
                                  className="deploy-site-env-input"
                                  placeholder="somevalue"
                                  value={env.value}
                                  onChange={(e) => fillEnvValue(e.target.value, i)}
                                />
                                <span
                                  className="remove-env-item"
                                  onClick={(e) => removeBuildEnvItem(i)}
                                >
                                  <FontAwesomeIcon
                                    icon={faTimesCircle}
                                  ></FontAwesomeIcon>
                                </span>
                              </div>
                            ))}
                          </div>
                        )}
                        <button
                          type="button"
                          className="add-new-var-button"
                          onClick={(e) => addBuildEnv()}
                        >
                          New Variable
                        </button>
                      </div>
                      {!selectedOrg?.wallet && !orgLoading ? (
                        <div className="wallet-details-container">
                          <div className="wallet-details-items">
                            <span className="exclamation-icon">
                              <FontAwesomeIcon
                                icon={faExclamationCircle}
                              ></FontAwesomeIcon>
                            </span>
                            <span>
                              You have to enable your organization wallet before you
                              can deploy your project.
                              <Link to="/dashboard/wallet">Enable now</Link>
                            </span>
                          </div>
                        </div>
                      ) : null}
                    </div>
                    <div className="button-container">
                      <button
                        type="button"
                        className="primary-button"
                        onClick={startDeployment}
                        disabled={deployDisabled}
                      >
                        {startDeploymentLoading && (
                          <BounceLoader size={20} color={"#fff"} loading={true} />
                        )}
                        Deploy
                      </button>
                      <button
                        type="button"
                        className="cancel-button"
                        onClick={(e) => setCreateDeployProgress(2)}
                      >
                        Back
                      </button>
                    </div>
                    {errorWarning ? (
                      <div className="warning-container">
                        <div className="warning-header">
                          <FontAwesomeIcon icon={faExclamationCircle} />{" "}
                          {errorMessage}
                        </div>
                      </div>
                    ) : null}
                  </>
                )}
              </div>
            </div>
          </div>
        </div>
      </main>
    </div>
  );
}
Example #6
Source File: DomainItem.tsx    From argo-react with MIT License 4 votes vote down vote up
DomainItem: React.FC<IDeploymentItemProps> = ({
  index,
  type,
  domainId,
  domain,
  link,
  isSubdomain,
  autoDns,
  uuid,
  ownerVerified,
  domainType,
}) => {
  const { projectLoading, selectedProject, selectedOrg } =
    useContext<IStateModel>(StateContext);
  const { fetchProject } = useContext<IActionModel>(ActionContext);

  const [editMode, setEditMode] = useState<boolean>(false);
  const [editDomainLoading, setEditDomainLoading] = useState<boolean>(false);
  const [verifyDomainLoading, setVerifyDomainLoading] = useState<boolean>(false);
  const [updateDomainLoading, setUpdateDomainLoading] = useState<boolean>(false);
  const [deleteDomainLoading, setDeleteDomainLoading] = useState<boolean>(false);
  const [editDomainName, setEditDomainName] = useState<string>(domain);
  const [isLatest, setIsLatest] = useState<boolean>(false);
  const [deployedSite, setDeployedSite] = useState<string>("");
  const sortedDeployments = projectLoading
    ? []
    : selectedProject?.deployments
        .filter((dep) => dep.sitePreview)
        .sort((a, b) => moment(b.createdAt).diff(moment(a.createdAt)));

  let separator = {
    base: "",
    sep: "",
  };

  if (link.indexOf("arweave.net") !== -1) {
    separator = {
      base: "arweave",
      sep: "https://arweave.net/",
    };
  } else if (link.indexOf("siasky.net") !== -1) {
    separator = {
      base: "sia",
      sep: "https://siasky.net/",
    };
  } else if (link.indexOf("ipfs.infura.io") !== -1) {
    separator = {
      base: "ipfs",
      sep: "https://ipfs.infura.io/ipfs/",
    };
  }

  useEffect(() => {
    if (domain) {
      setEditDomainName(domain);
    }
    if (link) {
      setDeployedSite(autoDns ? "latest" : link);
    }
  }, [domain, link, autoDns]);

  const editDomainDetails = () => {
    setEditDomainLoading(true);
    const domainBody = {
      orgId: selectedOrg?._id,
      name: editDomainName !== domain ? editDomainName : undefined,
      link:
        deployedSite === "latest"
          ? (sortedDeployments || [{ sitePreview: undefined }])[0]?.sitePreview
          : deployedSite !== link
          ? deployedSite
          : undefined,
      isLatest,
      projectId: selectedProject?._id,
      type: domainType,
    };
    ApiService.editDomain(domainId, domainBody).subscribe((result) => {
      if (result.success) {
        setEditMode(false);
        setEditDomainName("");
        setDeployedSite("");
        fetchProject(`${selectedProject?._id}`);
      } else {
        setEditMode(false);
        setEditDomainName("");
        setDeployedSite("");
      }
      setEditDomainLoading(false);
    });
  };

  const deleteDomain = () => {
    setDeleteDomainLoading(true);
    ApiService.deleteDomain(domainId).subscribe((result) => {
      if (result.success) {
        setEditDomainName("");
        setDeployedSite("");
        fetchProject(`${selectedProject?._id}`);
      } else {
        setEditDomainName("");
        setDeployedSite("");
      }
      setDeleteDomainLoading(false);
    });
  };

  const verifyDomain = () => {
    setVerifyDomainLoading(true);
    const verify = {
      id: domainId,
    };
    ApiService.verifyDomain(verify).subscribe((result) => {
      if (result.verified) {
        setEditDomainName("");
        setDeployedSite("");
        setVerifyDomainLoading(false);
        fetchProject(`${selectedProject?._id}`);
      } else {
        setEditDomainName("");
        setDeployedSite("");
        setVerifyDomainLoading(false);
      }
    });
  };

  const updateHnsDomain = () => {
    setUpdateDomainLoading(true);
    let records: string = "";
    if (!isSubdomain) {
      const records_json = [
        {
          type: "TXT",
          host: "_contenthash",
          value: `${separator.base}://${link.split(separator.sep)[1].split("/")[0]}`,
          ttl: 60,
        },
        {
          type: "ALIAS",
          host: "@",
          value: `${separator.base}.namebase.io.`,
          ttl: 3600,
        },
      ];

      records = btoa(JSON.stringify(records_json));
    } else {
      const records_json = [
        {
          type: "TXT",
          host: `_contenthash.${domain.substring(0, domain.lastIndexOf("."))}`,
          value: `${separator.base}://${link.split(separator.sep)[1].split("/")[0]}`,
          ttl: 60,
        },
        {
          type: "CNAME",
          host: domain.substring(0, domain.lastIndexOf(".")),
          value: `${separator.base}.namebase.io.`,
          ttl: 3600,
        },
      ];

      records = btoa(JSON.stringify(records_json));
    }
    const url = new URL(
      `https://namebase.io/next/domain-manager/${domain.substring(
        domain.lastIndexOf(".") + 1,
        domain.length,
      )}/records`,
    );
    const redirectUrl = window.location.href;
    const encodedRedirectUrl = encodeURIComponent(
      encodeURIComponent(redirectUrl.toString()),
    );

    url.searchParams.append("records", records);
    url.searchParams.append("redirect", encodedRedirectUrl);
    setUpdateDomainLoading(false);

    // console.log(url)
    window.location.href = url.toString();
  };

  const updateEnsDomain = async () => {
    try {
      await Web3Service.updateEnsContentHash(
        domain,
        `${separator.base}://${link.split(separator.sep)[1].split("/")[0]}`,
      );
    } catch (error) {}
  };

  const setTransaction = (tx: string) => {
    setDeployedSite(tx);

    if (tx === "latest") {
      setIsLatest(true);
    } else {
      setIsLatest(false);
    }
  };

  useEffect(() => {
    return () => {
      Web3Service.disconnect();
    };
  }, []);

  return (
    <div className="domain-item" key={index}>
      {type === "filled" &&
        (!editMode ? (
          <>
            <div className="domain-general-domain-item-header-container">
              <div className="domain-general-domain-item-header">
                <div className="domain-general-domain-item-header-left">
                  <div>
                    <a
                      href={`${
                        domainType.indexOf("handshake") !== -1 ? "http" : "https"
                      }://${domain}`}
                      target="_blank"
                      rel="noopener noreferrer"
                    >
                      {domain}
                      <span>
                        <FontAwesomeIcon icon={faExternalLinkAlt} />
                      </span>
                    </a>
                  </div>
                  {autoDns ? (
                    <div className="domain-tag-container">
                      <span className="domain-tag">Automated</span>
                    </div>
                  ) : null}
                </div>
                <div className="domain-general-domain-item-header-right">
                  <button className="edit-button" onClick={(e) => setEditMode(true)}>
                    Edit
                  </button>
                  <button
                    className="remove-button"
                    disabled={deleteDomainLoading}
                    onClick={deleteDomain}
                  >
                    <span>Remove</span>
                    {deleteDomainLoading ? (
                      <BounceLoader size={20} color={"#ee0902"} loading={true} />
                    ) : null}
                  </button>
                </div>
              </div>
            </div>
            <div className="domain-general-domain-item-body-container">
              <div className="domain-general-domain-item-body">
                <div className="domain-general-domain-item-body-item">
                  {!isSubdomain ? (
                    <>
                      <h3>Domain Configuration</h3>
                      {domainType.indexOf("handshake") === -1 &&
                      domainType.indexOf("ens") === -1 ? (
                        <p>
                          Set the following record on your DNS provider to configure
                          your domain and verify your ownership:
                        </p>
                      ) : domainType.indexOf("handshake") !== -1 ? (
                        <p>
                          Update the following record on Namebase to configure your
                          HNS domain and verify domain records:
                        </p>
                      ) : (
                        <p>
                          Update the following record on ENS Dashboard to configure
                          your ENS domain and verify domain records:
                        </p>
                      )}
                    </>
                  ) : (
                    <>
                      <h3>Subdomain Configuration</h3>
                      {domainType.indexOf("handshake") === -1 &&
                      domainType.indexOf("ens") === -1 ? (
                        <p>
                          Set the following record on your DNS provider to configure
                          your subdomain and verify your ownership:
                        </p>
                      ) : domainType.indexOf("handshake") !== -1 ? (
                        <p>
                          Update the following record on Namebase to configure your
                          HNS subdomain and verify subdomain records:
                        </p>
                      ) : (
                        <p>
                          Update the following record on ENS Dashboard to configure
                          your ENS subdomain and verify domain records:
                        </p>
                      )}
                    </>
                  )}
                  <div className="configure-domain-records-table">
                    <div className="thead">
                      <div className="tr">
                        <div
                          className={`th ${
                            domainType.indexOf("ens") !== -1 ? "more-width" : ""
                          }`}
                        >
                          Type
                        </div>
                        <div
                          className={`th ${
                            domainType.indexOf("ens") !== -1 ? "less-width" : ""
                          }`}
                        >
                          Name
                        </div>
                        <div className="th">Value</div>
                      </div>
                    </div>
                    {domainType.indexOf("handshake") === -1 &&
                    domainType.indexOf("ens") === -1 ? (
                      <div className="tbody">
                        <div className="tr">
                          <div className="td">A</div>
                          <div className="td">{domain}</div>
                          <div className="td">35.202.158.174</div>
                        </div>
                        <div className="tr">
                          <div className="td">TXT</div>
                          <div className="td">{domain}</div>
                          <div className="td">argo={uuid}</div>
                        </div>
                      </div>
                    ) : domainType.indexOf("ens") !== -1 ? (
                      <div className="tbody">
                        <div className="tr">
                          <div className="td more-width">CONTENT</div>
                          <div className="td less-width">{domain}</div>
                          <div className="td">
                            {separator.base}://
                            {link.split(separator.sep)[1].split("/")[0]}
                          </div>
                        </div>
                      </div>
                    ) : !isSubdomain ? (
                      <div className="tbody">
                        <div className="tr">
                          <div className="td">A</div>
                          <div className="td">@</div>
                          <div className="td">{separator.base}.namebase.io.</div>
                        </div>
                        <div className="tr">
                          <div className="td">TXT</div>
                          <div className="td">_contenthash</div>
                          <div className="td">
                            {separator.base}://
                            {link.split(separator.sep)[1].split("/")[0]}
                          </div>
                        </div>
                      </div>
                    ) : (
                      <div className="tbody">
                        <div className="tr">
                          <div className="td">CNAME</div>
                          <div className="td">{domain}</div>
                          <div className="td">{separator.base}.namebase.io.</div>
                        </div>
                        <div className="tr">
                          <div className="td">TXT</div>
                          <div className="td">
                            _contenthash.
                            {domain.substring(0, domain.lastIndexOf("."))}
                          </div>
                          <div className="td">
                            {separator.base}://
                            {link.split(separator.sep)[1].split("/")[0]}
                          </div>
                        </div>
                      </div>
                    )}
                  </div>
                  {!ownerVerified ? (
                    <div className="verify-domain-container">
                      <div className="verify-domain-text">
                        <span>
                          <FontAwesomeIcon icon={faTimesCircle}></FontAwesomeIcon>
                        </span>
                        <span>Update Records and Verify</span>
                      </div>
                      <div className="verify-domain-button-container">
                        {domainType.indexOf("handshake") !== -1 ? (
                          <button
                            className="update-domain-button"
                            disabled={updateDomainLoading}
                            onClick={updateHnsDomain}
                          >
                            Update
                            {updateDomainLoading ? (
                              <BounceLoader
                                size={20}
                                color={"#fff"}
                                loading={true}
                              />
                            ) : null}
                          </button>
                        ) : null}
                        {domainType.indexOf("ens") !== -1 ? (
                          <button
                            className="update-domain-button"
                            disabled={updateDomainLoading}
                            onClick={updateEnsDomain}
                          >
                            Update
                            {updateDomainLoading ? (
                              <BounceLoader
                                size={20}
                                color={"#fff"}
                                loading={true}
                              />
                            ) : null}
                          </button>
                        ) : null}
                        <button
                          className="verify-domain-button"
                          disabled={verifyDomainLoading}
                          onClick={verifyDomain}
                        >
                          Verify
                          {verifyDomainLoading ? (
                            <BounceLoader size={20} color={"#fff"} loading={true} />
                          ) : null}
                        </button>
                      </div>
                    </div>
                  ) : null}
                </div>
              </div>
            </div>
          </>
        ) : (
          <div className="domain-general-domain-item-edit">
            <div className="domain-general-domain-item-edit-container">
              <div className="domain-general-domain-item-edit-form">
                <span className="form-label">Domain</span>
                <input
                  type="text"
                  className="form-input"
                  placeholder="mywebsite.com"
                  value={editDomainName}
                  onChange={(e) => setEditDomainName(e.target.value)}
                />
              </div>
              <div className="domain-general-domain-item-edit-form">
                <span className="form-label">Site</span>
                <div className="form-select-container">
                  <select
                    className="form-select"
                    value={deployedSite}
                    onChange={(e) => setTransaction(e.target.value)}
                  >
                    <option value="">Select Site</option>
                    {domainType.indexOf("handshake") === -1 &&
                      domainType.indexOf("ens") === -1 && (
                        <option value="latest">Latest Deployed</option>
                      )}
                    {(sortedDeployments ? sortedDeployments : []).map(
                      (dep, index) => (
                        <option value={dep.sitePreview} key={index}>
                          {dep.sitePreview}
                        </option>
                      ),
                    )}
                  </select>
                  <span className="select-down-icon">
                    <FontAwesomeIcon icon={faChevronDown} />
                  </span>
                </div>
              </div>
              <div className="domain-general-domain-item-edit-button">
                <button
                  className="save-button"
                  disabled={!editDomainName || !deployedSite}
                  onClick={editDomainDetails}
                >
                  {editDomainLoading && (
                    <BounceLoader size={20} color={"#fff"} loading={true} />
                  )}
                  Save
                </button>
                <button
                  className="cancel-button"
                  onClick={(e) => setEditMode(false)}
                >
                  Cancel
                </button>
              </div>
            </div>
          </div>
        ))}
      {type === "skeleton" && (
        <>
          <div className="domain-general-domain-item-header-container">
            <div className="domain-general-domain-item-header">
              <div className="domain-general-domain-item-header-left">
                <Skeleton width={300} duration={2} />
              </div>
              <div className="domain-general-domain-item-header-right">
                <Skeleton width={120} duration={2} />
              </div>
            </div>
          </div>
        </>
      )}
    </div>
  );
}
Example #7
Source File: Editor.tsx    From zwo-editor with MIT License 4 votes vote down vote up
Editor = ({ match }: RouteComponentProps<TParams>) => {
  const { v4: uuidv4 } = require("uuid");

  const S3_URL = "https://zwift-workout.s3-eu-west-1.amazonaws.com";

  const [id, setId] = useState(
    match.params.id === "new"
      ? localStorage.getItem("id") || generateId()
      : match.params.id
  );
  const [bars, setBars] = useState<Array<Bar>>(
    JSON.parse(localStorage.getItem("currentWorkout") || "[]")
  );
  const [actionId, setActionId] = useState<string | undefined>(undefined);
  const [ftp, setFtp] = useState(
    parseInt(localStorage.getItem("ftp") || "200")
  );
  const [weight, setWeight] = useState(
    parseInt(localStorage.getItem("weight") || "75")
  );
  const [instructions, setInstructions] = useState<Array<Instruction>>(
    JSON.parse(localStorage.getItem("instructions") || "[]")
  );
  const [tags, setTags] = useState(
    JSON.parse(localStorage.getItem("tags") || "[]")
  );

  const [name, setName] = useState(localStorage.getItem("name") || "");
  const [description, setDescription] = useState(
    localStorage.getItem("description") || ""
  );
  const [author, setAuthor] = useState(localStorage.getItem("author") || "");

  const [savePopupIsVisile, setSavePopupVisibility] = useState(false);
  const [sharePopupIsVisile, setSharePopupVisibility] = useState(false);

  const [user, setUser] = useState<firebase.User | null>(null);
  const [visibleForm, setVisibleForm] = useState("login"); // default form is login

  const canvasRef = useRef<HTMLInputElement>(null);
  const segmentsRef = useRef<HTMLInputElement>(null);
  const [segmentsWidth, setSegmentsWidth] = useState(1320);

  const [message, setMessage] = useState<Message>();

  const [showWorkouts, setShowWorkouts] = useState(false);

  // bike or run
  const [sportType, setSportType] = useState<SportType>(
    (localStorage.getItem("sportType") as SportType) || "bike"
  );

  // distance or time
  const [durationType, setDurationType] = useState<DurationType>(
    (localStorage.getItem("durationType") as DurationType) || "time"
  );

  const [runningTimes, setRunningTimes] = useState(loadRunningTimes());

  const [textEditorIsVisible, setTextEditorIsVisible] = useState(false);
  const [selectedInstruction, setSelectedInstruction] = useState<Instruction>();

  const db = firebase.database();

  const location = useLocation();

  useEffect(() => {
    const params = new URLSearchParams(location.search);
    const mode = params.get("mode");
    if (mode === "resetPassword") {
      setVisibleForm("updatePassword");
      setSavePopupVisibility(true);
    }
  }, [location]);

  useEffect(() => {
    setMessage({ visible: true, class: "loading", text: "Loading.." });

    db.ref("workouts/" + id)
      .once("value")
      .then(function (snapshot) {
        if (snapshot.val()) {
          // workout exist on server
          setAuthor(snapshot.val().author);
          setName(snapshot.val().name);
          setDescription(snapshot.val().description);
          setBars(snapshot.val().workout || []);
          setInstructions(snapshot.val().instructions || []);
          setTags(snapshot.val().tags || []);
          setDurationType(snapshot.val().durationType);
          setSportType(snapshot.val().sportType);

          localStorage.setItem("id", id);
        } else {
          // workout doesn't exist on cloud
          if (id === localStorage.getItem("id")) {
            // user refreshed the page
          } else {
            // treat this as new workout
            setBars([]);
            setInstructions([]);
            setName("");
            setDescription("");
            setAuthor("");
            setTags([]);
          }

          localStorage.setItem("id", id);
        }
        console.log("useEffect firebase");

        //finished loading
        setMessage({ visible: false });
      });

    auth.onAuthStateChanged((user) => {
      if (user) {
        setUser(user);
      }
    });

    window.history.replaceState("", "", `/editor/${id}`);

    ReactGA.initialize("UA-55073449-9");
    ReactGA.pageview(window.location.pathname + window.location.search);
  }, [id, db]);

  useEffect(() => {
    localStorage.setItem("currentWorkout", JSON.stringify(bars));
    localStorage.setItem("ftp", ftp.toString());

    localStorage.setItem("instructions", JSON.stringify(instructions));
    localStorage.setItem("weight", weight.toString());

    localStorage.setItem("name", name);
    localStorage.setItem("description", description);
    localStorage.setItem("author", author);
    localStorage.setItem("tags", JSON.stringify(tags));
    localStorage.setItem("sportType", sportType);
    localStorage.setItem("durationType", durationType);

    localStorage.setItem("runningTimes", JSON.stringify(runningTimes));

    setSegmentsWidth(segmentsRef.current?.scrollWidth || 1320);
  }, [
    segmentsRef,
    bars,
    ftp,
    instructions,
    weight,
    name,
    description,
    author,
    tags,
    sportType,
    durationType,
    runningTimes,
  ]);

  function generateId() {
    return Math.random().toString(36).substr(2, 16);
  }

  function newWorkout() {
    console.log("New workout");

    setId(generateId());
    setBars([]);
    setInstructions([]);
    setName("");
    setDescription("");
    setAuthor("");
    setTags([]);
  }

  function handleOnChange(id: string, values: Bar) {
    const index = bars.findIndex((bar) => bar.id === id);

    const updatedArray = [...bars];
    updatedArray[index] = values;

    setBars(updatedArray);
  }

  function handleOnClick(id: string) {
    if (id === actionId) {
      setActionId(undefined);
    } else {
      setActionId(id);
    }
  }

  function handleKeyPress(event: React.KeyboardEvent<HTMLDivElement>) {
    if (event.target instanceof HTMLInputElement) {
      // Ignore key presses coming from input elements
      return;
    }

    if (event.target instanceof HTMLTextAreaElement) {
      // Ignore key presses coming from textarea elements
      return;
    }

    switch (event.keyCode) {
      case 8:
        removeBar(actionId || "");
        // Prevent navigation to previous page
        event.preventDefault();
        break;
      case 37:
        // reduce time
        removeTimeToBar(actionId || "");
        break;
      case 39:
        // add time
        addTimeToBar(actionId || "");
        break;
      case 38:
        // add power
        addPowerToBar(actionId || "");
        break;
      case 40:
        // add power
        removePowerToBar(actionId || "");
        break;
      default:
        //console.log(event.keyCode);
        break;
    }
  }

  function addBar(
    zone: number,
    duration: number = 300,
    cadence: number = 0,
    pace: number = 0,
    length: number = 200
  ) {
    setBars((bars) => [
      ...bars,
      {
        time:
          durationType === "time"
            ? duration
            : helpers.round(
                helpers.calculateTime(length, calculateSpeed(pace)),
                1
              ),
        length:
          durationType === "time"
            ? helpers.round(
                helpers.calculateDistance(duration, calculateSpeed(pace)),
                1
              )
            : length,
        power: zone,
        cadence: cadence,
        type: "bar",
        id: uuidv4(),
        pace: pace,
      },
    ]);
  }

  function addTrapeze(
    zone1: number,
    zone2: number,
    duration: number = 300,
    pace: number = 0,
    length: number = 1000,
    cadence: number = 0
  ) {
    setBars((bars) => [
      ...bars,
      {
        time:
          durationType === "time"
            ? duration
            : helpers.round(
                helpers.calculateTime(length, calculateSpeed(pace)),
                1
              ),
        length:
          durationType === "time"
            ? helpers.round(
                helpers.calculateDistance(duration, calculateSpeed(pace)),
                1
              )
            : length,
        startPower: zone1,
        endPower: zone2,
        cadence: cadence,
        pace: pace,
        type: "trapeze",
        id: uuidv4(),
      },
    ]);
  }

  function addFreeRide(
    duration: number = 600,
    cadence: number = 0,
    length: number = 1000
  ) {
    setBars((bars) => [
      ...bars,
      {
        time: durationType === "time" ? duration : 0,
        length: durationType === "time" ? 0 : length,
        cadence: cadence,
        type: "freeRide",
        id: uuidv4(),
      },
    ]);
  }

  function addInterval(
    repeat: number = 3,
    onDuration: number = 30,
    offDuration: number = 120,
    onPower: number = 1,
    offPower: number = 0.5,
    cadence: number = 0,
    restingCadence: number = 0,
    pace: number = 0,
    onLength: number = 200,
    offLength: number = 200
  ) {
    setBars((bars) => [
      ...bars,
      {
        time:
          durationType === "time"
            ? (onDuration + offDuration) * repeat
            : helpers.round(
                helpers.calculateTime(
                  (onLength + offLength) * repeat,
                  calculateSpeed(pace)
                ),
                1
              ),
        length:
          durationType === "time"
            ? helpers.round(
                helpers.calculateDistance(
                  (onDuration + offDuration) * repeat,
                  calculateSpeed(pace)
                ),
                1
              )
            : (onLength + offLength) * repeat,
        id: uuidv4(),
        type: "interval",
        cadence: cadence,
        restingCadence: restingCadence,
        repeat: repeat,
        onDuration:
          durationType === "time"
            ? onDuration
            : helpers.round(
                helpers.calculateTime(
                  (onLength * 1) / onPower,
                  calculateSpeed(pace)
                ),
                1
              ),
        offDuration:
          durationType === "time"
            ? offDuration
            : helpers.round(
                helpers.calculateTime(
                  (offLength * 1) / offPower,
                  calculateSpeed(pace)
                ),
                1
              ),
        onPower: onPower,
        offPower: offPower,
        pace: pace,
        onLength:
          durationType === "time"
            ? helpers.round(
                helpers.calculateDistance(
                  (onDuration * 1) / onPower,
                  calculateSpeed(pace)
                ),
                1
              )
            : onLength,
        offLength:
          durationType === "time"
            ? helpers.round(
                helpers.calculateDistance(
                  (offDuration * 1) / offPower,
                  calculateSpeed(pace)
                ),
                1
              )
            : offLength,
      },
    ]);
  }

  function addInstruction(text = "", time = 0, length = 0) {
    setInstructions((instructions) => [
      ...instructions,
      {
        text: text,
        time: time,
        length: length,
        id: uuidv4(),
      },
    ]);
  }

  function changeInstruction(id: string, values: Instruction) {
    const index = instructions.findIndex(
      (instructions) => instructions.id === id
    );

    const updatedArray = [...instructions];
    updatedArray[index] = values;
    setInstructions(updatedArray);
  }

  function deleteInstruction(id: string) {
    const updatedArray = [...instructions];
    setInstructions(updatedArray.filter((item) => item.id !== id));
  }

  function removeBar(id: string) {
    const updatedArray = [...bars];
    setBars(updatedArray.filter((item) => item.id !== id));
    setActionId(undefined);
  }

  function addTimeToBar(id: string) {
    const updatedArray = [...bars];

    const index = updatedArray.findIndex((bar) => bar.id === id);
    const element = updatedArray[index];
    if (element && durationType === "time") {
      element.time = element.time + 5;
      element.length =
        (helpers.calculateDistance(
          element.time,
          calculateSpeed(element.pace || 0)
        ) *
          1) /
        (element.power || 1);
      setBars(updatedArray);
    }

    if (element && durationType === "distance") {
      element.length = (element.length || 0) + 200;
      element.time =
        (helpers.calculateTime(
          element.length,
          calculateSpeed(element.pace || 0)
        ) *
          1) /
        (element.power || 1);
      setBars(updatedArray);
    }
  }

  function removeTimeToBar(id: string) {
    const updatedArray = [...bars];

    const index = updatedArray.findIndex((bar) => bar.id === id);
    const element = updatedArray[index];
    if (element && element.time > 5 && durationType === "time") {
      element.time = element.time - 5;
      element.length =
        (helpers.calculateDistance(
          element.time,
          calculateSpeed(element.pace || 0)
        ) *
          1) /
        (element.power || 1);
      setBars(updatedArray);
    }

    if (element && (element.length || 0) > 200 && durationType === "distance") {
      element.length = (element.length || 0) - 200;
      element.time =
        (helpers.calculateTime(
          element.length,
          calculateSpeed(element.pace || 0)
        ) *
          1) /
        (element.power || 1);
      setBars(updatedArray);
    }
  }

  function addPowerToBar(id: string) {
    const updatedArray = [...bars];

    const index = updatedArray.findIndex((bar) => bar.id === id);
    const element = updatedArray[index];
    if (element && element.power) {
      element.power = parseFloat((element.power + 1 / ftp).toFixed(3));

      if (durationType === "time") {
        element.length =
          (helpers.calculateDistance(
            element.time,
            calculateSpeed(element.pace || 0)
          ) *
            1) /
          element.power;
      } else {
        element.time =
          (helpers.calculateTime(
            element.length,
            calculateSpeed(element.pace || 0)
          ) *
            1) /
          element.power;
      }

      setBars(updatedArray);
    }
  }

  function removePowerToBar(id: string) {
    const updatedArray = [...bars];

    const index = updatedArray.findIndex((bar) => bar.id === id);
    const element = updatedArray[index];
    if (element && element.power && element.power >= Zones.Z1.min) {
      element.power = parseFloat((element.power - 1 / ftp).toFixed(3));

      if (durationType === "time") {
        element.length =
          (helpers.calculateDistance(
            element.time,
            calculateSpeed(element.pace || 0)
          ) *
            1) /
          element.power;
      } else {
        element.time =
          (helpers.calculateTime(
            element.length,
            calculateSpeed(element.pace || 0)
          ) *
            1) /
          element.power;
      }

      setBars(updatedArray);
    }
  }

  function duplicateBar(id: string) {
    const index = bars.findIndex((bar) => bar.id === id);
    const element = [...bars][index];

    if (element.type === "bar")
      addBar(
        element.power || 80,
        element.time,
        element.cadence,
        element.pace,
        element.length
      );
    if (element.type === "freeRide")
      addFreeRide(element.time, element.cadence, element.length);
    if (element.type === "trapeze")
      addTrapeze(
        element.startPower || 80,
        element.endPower || 160,
        element.time,
        element.pace || 0,
        element.length,
        element.cadence
      );
    if (element.type === "interval")
      addInterval(
        element.repeat,
        element.onDuration,
        element.offDuration,
        element.onPower,
        element.offPower,
        element.cadence,
        element.restingCadence,
        element.pace,
        element.onLength,
        element.offLength
      );

    setActionId(undefined);
  }

  function moveLeft(id: string) {
    const index = bars.findIndex((bar) => bar.id === id);
    // not first position of array
    if (index > 0) {
      const updatedArray = [...bars];
      const element = [...bars][index];
      updatedArray.splice(index, 1);
      updatedArray.splice(index - 1, 0, element);
      setBars(updatedArray);
    }
  }

  function moveRight(id: string) {
    const index = bars.findIndex((bar) => bar.id === id);
    // not first position of array
    if (index < bars.length - 1) {
      const updatedArray = [...bars];
      const element = [...bars][index];
      updatedArray.splice(index, 1);
      updatedArray.splice(index + 1, 0, element);
      setBars(updatedArray);
    }
  }

  function saveWorkout() {
    setSavePopupVisibility(true);
  }

  function deleteWorkout() {
    // save to cloud (firebase) if logged in
    if (user) {
      const itemsRef = firebase.database().ref();

      var updates: any = {};
      updates[`users/${user.uid}/workouts/${id}`] = null;
      updates[`workouts/${id}`] = null;

      // save to firebase
      itemsRef
        .update(updates)
        .then(() => {
          newWorkout();
        })
        .catch((error) => {
          console.log(error);
          setMessage({
            visible: true,
            class: "error",
            text: "Cannot delete workout",
          });
        });
    }
  }

  function shareWorkout() {
    if (user) {
      save();
      setSharePopupVisibility(true);
    } else {
      saveWorkout();
    }
  }

  function save() {
    setMessage({ visible: true, class: "loading", text: "Saving.." });

    const xml = createWorkoutXml({
      author,
      name,
      description,
      sportType,
      durationType,
      tags,
      bars,
      instructions,
    });

    const file = new Blob([xml], { type: "application/xml" });

    // save to cloud (firebase) if logged in
    if (user) {
      const itemsRef = firebase.database().ref();

      const item = {
        id: id,
        name: name,
        description: description,
        author: author,
        workout: bars,
        tags: tags,
        instructions: instructions,
        userId: user.uid,
        updatedAt: Date(),
        sportType: sportType,
        durationType: durationType,
      };

      const item2 = {
        name: name,
        description: description,
        updatedAt: Date(),
        sportType: sportType,
        durationType: durationType,
        workoutTime: helpers.formatDuration(
          helpers.getWorkoutLength(bars, durationType)
        ),
        workoutDistance: helpers.getWorkoutDistance(bars),
      };

      var updates: any = {};
      updates[`users/${user.uid}/workouts/${id}`] = item2;
      updates[`workouts/${id}`] = item;

      // save to firebase
      itemsRef
        .update(updates)
        .then(() => {
          //upload to s3
          upload(file, false);
          setMessage({ visible: false });
        })
        .catch((error) => {
          console.log(error);
          setMessage({
            visible: true,
            class: "error",
            text: "Cannot save this",
          });
        });
    } else {
      // download workout without saving
      setMessage({ visible: false });
    }

    return file;
  }

  function logout() {
    console.log("logout");

    auth.signOut().then(() => setUser(null));
  }

  function downloadWorkout() {
    const tempFile = save();
    const url = window.URL.createObjectURL(tempFile);

    var a = document.createElement("a");
    document.body.appendChild(a);
    a.style.display = "none";
    a.href = url;
    a.download = `${id}.zwo`;
    a.click();
    window.URL.revokeObjectURL(url);
  }

  function handleUpload(file: Blob) {
    // ask user if they want to overwrite current workout first
    if (bars.length > 0) {
      if (!window.confirm("Are you sure you want to create a new workout?")) {
        return false;
      }
    }

    newWorkout();
    upload(file, true);
  }

  function upload(file: Blob, parse = false) {
    fetch(process.env.REACT_APP_UPLOAD_FUNCTION || "https://zwiftworkout.netlify.app/.netlify/functions/upload", {
      method: "POST",
      body: JSON.stringify({
        fileType: "zwo",
        fileName: `${id}.zwo`,
      }),
    })
      .then((res) => res.json())
      .then(function (data) {
        const signedUrl = data.uploadURL;

        // upload to S3
        fetch(signedUrl, {
          method: "PUT",
          headers: {
            "Content-Type": "zwo",
          },
          body: file,
        })
          .then((response) => response.text())
          .then((data) => {
            console.log("File uploaded");

            // can parse now

            if (parse) fetchAndParse(id);
          })
          .catch((error) => {
            console.error(error);
          });
      });
  }

  function fetchAndParse(id: string) {
    // remove previous workout

    // TODO fix for running distance based
    setBars([]);
    setInstructions([]);

    fetch(`${S3_URL}/${id}.zwo`)
      .then((response) => response.text())
      .then((data) => {
        // remove xml comments
        data = data.replace(/<!--(.*?)-->/gm, "");

        //now parse file
        const workout = Converter.xml2js(data);
        const workout_file = workout.elements[0];

        if (workout_file.name === "workout_file") {
          // file is valid
          const authorIndex = workout_file.elements.findIndex(
            (element: { name: string }) => element.name === "author"
          );
          if (
            authorIndex !== -1 &&
            workout_file.elements[authorIndex].elements
          ) {
            setAuthor(workout_file.elements[authorIndex].elements[0].text);
          }

          const nameIndex = workout_file.elements.findIndex(
            (element: { name: string }) => element.name === "name"
          );
          if (nameIndex !== -1 && workout_file.elements[nameIndex].elements) {
            setName(workout_file.elements[nameIndex].elements[0].text);
          }

          const descriptionIndex = workout_file.elements.findIndex(
            (element: { name: string }) => element.name === "description"
          );
          if (
            descriptionIndex !== -1 &&
            workout_file.elements[descriptionIndex].elements
          ) {
            setDescription(
              workout_file.elements[descriptionIndex].elements[0].text
            );
          }

          const workoutIndex = workout_file.elements.findIndex(
            (element: { name: string }) => element.name === "workout"
          );

          var totalTime = 0;

          workout_file.elements[workoutIndex].elements.map(
            (w: {
              name: string;
              attributes: {
                Power: any;
                PowerLow: string;
                Duration: string;
                PowerHigh: string;
                Cadence: string;
                CadenceResting: string;
                Repeat: string;
                OnDuration: string;
                OffDuration: string;
                OnPower: string;
                OffPower: string;
                Pace: string;
              };
              elements: any;
            }) => {
              let duration = parseFloat(w.attributes.Duration);

              if (w.name === "SteadyState")
                addBar(
                  parseFloat(w.attributes.Power || w.attributes.PowerLow),
                  parseFloat(w.attributes.Duration),
                  parseFloat(w.attributes.Cadence || "0"),
                  parseInt(w.attributes.Pace || "0")
                );

              if (
                w.name === "Ramp" ||
                w.name === "Warmup" ||
                w.name === "Cooldown"
              )
                addTrapeze(
                  parseFloat(w.attributes.PowerLow),
                  parseFloat(w.attributes.PowerHigh),
                  parseFloat(w.attributes.Duration),
                  parseInt(w.attributes.Pace || "0"),
                  undefined,
                  parseInt(w.attributes.Cadence)
                );

              if (w.name === "IntervalsT") {
                addInterval(
                  parseFloat(w.attributes.Repeat),
                  parseFloat(w.attributes.OnDuration),
                  parseFloat(w.attributes.OffDuration),
                  parseFloat(w.attributes.OnPower),
                  parseFloat(w.attributes.OffPower),
                  parseInt(w.attributes.Cadence || "0"),
                  parseInt(w.attributes.CadenceResting),
                  parseInt(w.attributes.Pace || "0")
                );
                duration =
                  (parseFloat(w.attributes.OnDuration) +
                    parseFloat(w.attributes.OffDuration)) *
                  parseFloat(w.attributes.Repeat);
              }

              if (w.name === "FreeRide")
                addFreeRide(
                  parseFloat(w.attributes.Duration),
                  parseInt(w.attributes.Cadence)
                );

              // check for instructions
              const textElements = w.elements;
              if (textElements && textElements.length > 0) {
                textElements.map(
                  (t: {
                    name: string;
                    attributes: {
                      message: string | undefined;
                      timeoffset: string;
                    };
                  }) => {
                    if (t.name.toLowerCase() === "textevent")
                      addInstruction(
                        t.attributes.message,
                        totalTime + parseFloat(t.attributes.timeoffset)
                      );

                    return false;
                  }
                );
              }

              totalTime = totalTime + duration;
              // map functions expect return value
              return false;
            }
          );
        }
      })
      .catch((error) => {
        console.error(error);
      });
  }

  function calculateSpeed(pace: number = 0) {
    if (sportType === "bike") {
      return 0;
    } else {
      // return speed in m/s
      // speed  = distance / time
      const distances = [1.60934, 5, 10, 21.0975, 42.195];
      const times = [
        runningTimes.oneMile,
        runningTimes.fiveKm,
        runningTimes.tenKm,
        runningTimes.halfMarathon,
        runningTimes.marathon,
      ];

      return (distances[pace] * 1000) / helpers.getTimeinSeconds(times[pace]);
    }
  }

  const renderBar = (bar: Bar) => (
    <Bar
      key={bar.id}
      id={bar.id}
      time={bar.time}
      length={bar.length || 200}
      power={bar.power || 100}
      cadence={bar.cadence}
      ftp={ftp}
      weight={weight}
      sportType={sportType}
      durationType={durationType}
      pace={bar.pace || 0}
      speed={calculateSpeed(bar.pace || 0)}
      onChange={(id: string, value: any) => handleOnChange(id, value)} // Change any to Interface Bar?
      onClick={(id: string) => handleOnClick(id)}
      selected={bar.id === actionId}
      showLabel={true}
    />
  );

  const renderTrapeze = (bar: Bar) => (
    <Trapeze
      key={bar.id}
      id={bar.id}
      time={bar.time}
      length={bar.length || 200}
      cadence={bar.cadence}
      startPower={bar.startPower || 80}
      endPower={bar.endPower || 160}
      ftp={ftp}
      sportType={sportType}
      durationType={durationType}
      pace={bar.pace || 0}
      speed={calculateSpeed(bar.pace || 0)}
      onChange={(id: string, value: any) => handleOnChange(id, value)} // Change any to Interface Bar?
      onClick={(id: string) => handleOnClick(id)}
      selected={bar.id === actionId}
    />
  );

  const renderFreeRide = (bar: Bar) => (
    <FreeRide
      key={bar.id}
      id={bar.id}
      time={bar.time}
      length={bar.length}
      cadence={bar.cadence}
      durationType={durationType}
      sportType={sportType}
      onChange={(id: string, value: any) => handleOnChange(id, value)} // Change any to Interface Bar?
      onClick={(id: string) => handleOnClick(id)}
      selected={bar.id === actionId}
    />
  );

  const renderInterval = (bar: Bar) => (
    <Interval
      key={bar.id}
      id={bar.id}
      repeat={bar.repeat || 3}
      onDuration={bar.onDuration || 10}
      offDuration={bar.offDuration || 50}
      onPower={bar.onPower || 250}
      offPower={bar.offPower || 120}
      onLength={bar.onLength || 200}
      offLength={bar.offLength || 200}
      cadence={bar.cadence}
      restingCadence={bar.restingCadence || 0}
      ftp={ftp}
      weight={weight}
      sportType={sportType}
      durationType={durationType}
      pace={bar.pace || 0}
      speed={calculateSpeed(bar.pace || 0)}
      handleIntervalChange={(id: string, value: any) =>
        handleOnChange(id, value)
      }
      handleIntervalClick={(id: string) => handleOnClick(id)}
      selected={bar.id === actionId}
    />
  );

  const renderComment = (instruction: Instruction, index: number) => (
    <Comment
      key={instruction.id}
      instruction={instruction}
      durationType={durationType}
      width={
        durationType === "distance"
          ? parseInt(helpers.getWorkoutDistance(bars)) * 100
          : helpers.getWorkoutLength(bars, durationType) / 3
      }
      onChange={(id: string, values: Instruction) =>
        changeInstruction(id, values)
      }
      onClick={(id: string) =>
        setSelectedInstruction(instructions.find((i) => i.id === id))
      }
      index={index}
    />
  );

  const renderRegistrationForm = () => {
    switch (visibleForm) {
      case "login":
        return (
          <LoginForm
            login={setUser}
            showSignup={() => setVisibleForm("signup")}
            dismiss={() => setSavePopupVisibility(false)}
            showForgotPassword={() => setVisibleForm("forgotPassword")}
          />
        );
      case "signup":
        return (
          <SignupForm
            signUp={setUser}
            showLogin={() => setVisibleForm("login")}
            dismiss={() => setSavePopupVisibility(false)}
          />
        );
      case "forgotPassword":
        return (
          <ForgotPasswordForm
            dismiss={() => setSavePopupVisibility(false)}
            workoutId={id}
          />
        );
      case "updatePassword":
        return (
          <UpdatePasswordForm
            dismiss={() => {
              setVisibleForm("login");
              setSavePopupVisibility(true);
            }}
          />
        );
      default:
        break;
    }
  };

  function setPace(value: string, id: string) {
    const index = bars.findIndex((bar) => bar.id === id);

    if (index !== -1) {
      const updatedArray = [...bars];
      const element = [...updatedArray][index];
      element.pace = parseInt(value);

      if (durationType === "time") {
        element.length =
          (helpers.calculateDistance(
            element.time,
            calculateSpeed(element.pace || 0)
          ) *
            1) /
          (element.power || 1);
      } else {
        element.time =
          (helpers.calculateTime(
            element.length,
            calculateSpeed(element.pace || 0)
          ) *
            1) /
          (element.power || 1);
      }

      setBars(updatedArray);
    }
  }

  function getPace(id: string) {
    const index = bars.findIndex((bar) => bar.id === id);

    if (index !== -1) {
      const element = [...bars][index];
      return element.pace;
    }
  }

  function switchSportType(newSportType: SportType) {
    setSportType(newSportType);
    setDurationType(newSportType === "bike" ? "time" : "distance");
  }

  function toggleTextEditor() {
    if (bars.length > 0 && !textEditorIsVisible) {
      if (
        window.confirm(
          "Editing a workout from the text editor will overwrite current workout"
        )
      )
        setTextEditorIsVisible(!textEditorIsVisible);
    } else {
      setTextEditorIsVisible(!textEditorIsVisible);
    }
  }

  function transformTextToWorkout(textValue: string) {
    // reset each time
    setBars([]);
    setInstructions([]);

    // 2 minutes block at 112% FTP
    // 2 minutes block at 330 W
    // 30 seconds block at ..

    //console.log(textValue);

    const workoutBlocks = textValue.split("\n");
    workoutBlocks.forEach((workoutBlock) => {
      if (workoutBlock.includes("steady")) {
        // generate a steady state block

        // extract watts
        const powerInWatts = workoutBlock.match(/([0-9]\d*w)/);
        const powerInWattsPerKg = workoutBlock.match(/([0-9]*.?[0-9]wkg)/);
        const powerInPercentageFtp = workoutBlock.match(/([0-9]\d*%)/);

        let power = powerInWatts ? parseInt(powerInWatts[0]) / ftp : 1;
        power = powerInWattsPerKg
          ? (parseFloat(powerInWattsPerKg[0]) * weight) / ftp
          : power;
        power = powerInPercentageFtp
          ? parseInt(powerInPercentageFtp[0]) / 100
          : power;

        // extract duration in seconds
        const durationInSeconds = workoutBlock.match(/([0-9]\d*s)/);
        const durationInMinutes = workoutBlock.match(/([0-9]*:?[0-9][0-9]*m)/);

        let duration = durationInSeconds && parseInt(durationInSeconds[0]);
        duration = durationInMinutes
          ? parseInt(durationInMinutes[0].split(":")[0]) * 60 +
            (parseInt(durationInMinutes[0].split(":")[1]) || 0)
          : duration;

        // extract cadence in rpm
        const cadence = workoutBlock.match(/([0-9]\d*rpm)/);
        const rpm = cadence ? parseInt(cadence[0]) : undefined;

        // extract multiplier
        // const multiplier = workoutBlock.match(/([0-9]\d*x)/)
        // const nTimes = multiplier ? Array(parseInt(multiplier[0])) : Array(1)
        // for (var i = 0; i < nTimes.length; i++)

        addBar(power, duration || 300, rpm);
      }

      if (
        workoutBlock.includes("ramp") ||
        workoutBlock.includes("warmup") ||
        workoutBlock.includes("cooldown")
      ) {
        // generate a steady ramp block

        // extract watts
        const startPowerInWatts = workoutBlock.match(/([0-9]\d*w)/);
        const startPowerInWattsPerKg = workoutBlock.match(/([0-9]*.?[0-9]wkg)/);
        const startPowerInPercentageFtp = workoutBlock.match(/([0-9]\d*%)/);

        let startPower = startPowerInWatts
          ? parseInt(startPowerInWatts[0]) / ftp
          : 1;
        startPower = startPowerInWattsPerKg
          ? (parseFloat(startPowerInWattsPerKg[0]) * weight) / ftp
          : startPower;
        startPower = startPowerInPercentageFtp
          ? parseInt(startPowerInPercentageFtp[0]) / 100
          : startPower;

        // extract watts
        const endPowerInWatts = workoutBlock.match(/(-[0-9]\d*w)/);
        const endPowerInWattsPerKg = workoutBlock.match(/(-[0-9]*.?[0-9]wkg)/);
        const endPowerInPercentageFtp = workoutBlock.match(/-([0-9]\d*%)/);

        let endPower = endPowerInWatts
          ? Math.abs(parseInt(endPowerInWatts[0])) / ftp
          : 1;
        endPower = endPowerInWattsPerKg
          ? (Math.abs(parseFloat(endPowerInWattsPerKg[0])) * weight) / ftp
          : endPower;
        endPower = endPowerInPercentageFtp
          ? Math.abs(parseInt(endPowerInPercentageFtp[0])) / 100
          : endPower;

        const durationInSeconds = workoutBlock.match(/([0-9]\d*s)/);
        const durationInMinutes = workoutBlock.match(/([0-9]*:?[0-9][0-9]*m)/);

        let duration = durationInSeconds && parseInt(durationInSeconds[0]);
        duration = durationInMinutes
          ? parseInt(durationInMinutes[0].split(":")[0]) * 60 +
            (parseInt(durationInMinutes[0].split(":")[1]) || 0)
          : duration;

        // extract cadence in rpm
        const cadence = workoutBlock.match(/([0-9]\d*rpm)/);
        const rpm = cadence ? parseInt(cadence[0]) : undefined;

        addTrapeze(
          startPower,
          endPower,
          duration || 300,
          undefined,
          undefined,
          rpm
        );
      }

      if (workoutBlock.includes("freeride")) {
        const durationInSeconds = workoutBlock.match(/([0-9]\d*s)/);
        const durationInMinutes = workoutBlock.match(/([0-9]*:?[0-9][0-9]*m)/);

        let duration = durationInSeconds && parseInt(durationInSeconds[0]);
        duration = durationInMinutes
          ? parseInt(durationInMinutes[0].split(":")[0]) * 60 +
            (parseInt(durationInMinutes[0].split(":")[1]) || 0)
          : duration;

        // extract cadence in rpm
        const cadence = workoutBlock.match(/([0-9]\d*rpm)/);
        const rpm = cadence ? parseInt(cadence[0]) : undefined;

        addFreeRide(duration || 600, rpm);
      }

      if (workoutBlock.includes("interval")) {
        const multiplier = workoutBlock.match(/([0-9]\d*x)/);
        const nTimes = multiplier ? parseInt(multiplier[0]) : 3;

        const durationInSeconds = workoutBlock.match(/([0-9]\d*s)/);
        const durationInMinutes = workoutBlock.match(/([0-9]*:?[0-9][0-9]*m)/);

        let duration = durationInSeconds && parseInt(durationInSeconds[0]);
        duration = durationInMinutes
          ? parseInt(durationInMinutes[0].split(":")[0]) * 60 +
            (parseInt(durationInMinutes[0].split(":")[1]) || 0)
          : duration;

        const offDurationInSeconds = workoutBlock.match(/(-[0-9]\d*s)/);
        const offDurationInMinutes = workoutBlock.match(
          /(-[0-9]*:?[0-9][0-9]*m)/
        );

        let offDuration =
          offDurationInSeconds && Math.abs(parseInt(offDurationInSeconds[0]));
        offDuration = offDurationInMinutes
          ? Math.abs(parseInt(offDurationInMinutes[0].split(":")[0])) * 60 +
            (parseInt(offDurationInMinutes[0].split(":")[1]) || 0)
          : offDuration;

        // extract watts
        const startPowerInWatts = workoutBlock.match(/([0-9]\d*w)/);
        const startPowerInWattsPerKg = workoutBlock.match(/([0-9]*.?[0-9]wkg)/);
        const startPowerInPercentageFtp = workoutBlock.match(/([0-9]\d*%)/);

        let startPower = startPowerInWatts
          ? parseInt(startPowerInWatts[0]) / ftp
          : 1;
        startPower = startPowerInWattsPerKg
          ? (parseFloat(startPowerInWattsPerKg[0]) * weight) / ftp
          : startPower;
        startPower = startPowerInPercentageFtp
          ? parseInt(startPowerInPercentageFtp[0]) / 100
          : startPower;

        // extract watts
        const endPowerInWatts = workoutBlock.match(/(-[0-9]\d*w)/);
        const endPowerInWattsPerKg = workoutBlock.match(/(-[0-9]*.?[0-9]wkg)/);
        const endPowerInPercentageFtp = workoutBlock.match(/-([0-9]\d*%)/);

        let endPower = endPowerInWatts
          ? Math.abs(parseInt(endPowerInWatts[0])) / ftp
          : 0.5;
        endPower = endPowerInWattsPerKg
          ? (Math.abs(parseFloat(endPowerInWattsPerKg[0])) * weight) / ftp
          : endPower;
        endPower = endPowerInPercentageFtp
          ? Math.abs(parseInt(endPowerInPercentageFtp[0])) / 100
          : endPower;

        // extract cadence in rpm
        const cadence = workoutBlock.match(/([0-9]\d*rpm)/);
        const rpm = cadence ? parseInt(cadence[0]) : undefined;

        const restingCadence = workoutBlock.match(/(-[0-9]\d*rpm)/);
        const restingRpm = restingCadence
          ? Math.abs(parseInt(restingCadence[0]))
          : undefined;

        addInterval(
          nTimes,
          duration || 30,
          offDuration || 120,
          startPower,
          endPower,
          rpm,
          restingRpm
        );
      }

      if (workoutBlock.includes("message")) {
        // extract message
        const message = workoutBlock.match(/["'](.*?)["']/);
        const text = message ? message[0] : "";

        const durationInSeconds = workoutBlock.match(/([0-9]\d*s)/);
        const durationInMinutes = workoutBlock.match(/([0-9]*:?[0-9][0-9]*m)/);

        let duration = durationInSeconds && parseInt(durationInSeconds[0]);
        duration = durationInMinutes
          ? parseInt(durationInMinutes[0].split(":")[0]) * 60 +
            (parseInt(durationInMinutes[0].split(":")[1]) || 0)
          : duration;

        addInstruction(text, duration || 0);
      }
    });
  }

  return (
    // Adding tabIndex allows div element to receive keyboard events
    <div className="container" onKeyDown={handleKeyPress} tabIndex={0}>
      <Helmet>
        <title>
          {name
            ? `${name} - Zwift Workout Editor`
            : "My Workout - Zwift Workout Editor"}
        </title>
        <meta name="description" content={description} />
        <meta
          property="og:title"
          content={
            name
              ? `${name} - Zwift Workout Editor`
              : "My Workout - Zwift Workout Editor"
          }
        />
        <meta property="og:description" content={description} />
        <link
          rel="canonical"
          href={`https://www.zwiftworkout.com/editor/${id}`}
        />
        <meta
          property="og:url"
          content={`https://www.zwiftworkout.com/editor/${id}`}
        />
      </Helmet>

      {message?.visible && (
        <div className={`message ${message.class}`}>
          {message.text}
          <button
            className="close"
            onClick={() => setMessage({ visible: false })}
          >
            <FontAwesomeIcon icon={faTimesCircle} size="lg" fixedWidth />
          </button>
        </div>
      )}

      {showWorkouts && (
        <Popup width="500px" dismiss={() => setShowWorkouts(false)}>
          {user ? <Workouts userId={user.uid} /> : renderRegistrationForm()}
        </Popup>
      )}

      {selectedInstruction && (
        <EditComment
          instruction={selectedInstruction}
          onChange={(id: string, values: Instruction) => {
            changeInstruction(id, values);
            setSelectedInstruction(undefined);
          }}
          dismiss={() => setSelectedInstruction(undefined)}
          onDelete={(id: string) => {
            deleteInstruction(id);
            setSelectedInstruction(undefined);
          }}
        />
      )}

      {savePopupIsVisile && (
        <Popup width="500px" dismiss={() => setSavePopupVisibility(false)}>
          {user ? (
            <SaveForm
              name={name}
              description={description}
              author={author}
              tags={tags}
              onNameChange={setName}
              onDescriptionChange={setDescription}
              onAuthorChange={setAuthor}
              onTagsChange={setTags}
              onSave={() => {
                save();
                setSavePopupVisibility(false);
              }}
              onDismiss={() => setSavePopupVisibility(false)}
              onLogout={logout}
            />
          ) : (
            renderRegistrationForm()
          )}
        </Popup>
      )}
      {sharePopupIsVisile && (
        <Popup width="500px" dismiss={() => setSharePopupVisibility(false)}>
          <ShareForm id={id} onDismiss={() => setSharePopupVisibility(false)} />
        </Popup>
      )}
      <div className="info">
        <div className="title">
          <h1>{name}</h1>
          <div className="description">{description}</div>
          <p>{author ? `by ${author}` : ""}</p>
        </div>
        <div className="workout">
          <div className="form-input">
            <label>Workout Time</label>
            <input
              className="textInput"
              value={helpers.formatDuration(
                helpers.getWorkoutLength(bars, durationType)
              )}
              disabled
            />
          </div>
          {sportType === "run" && (
            <div className="form-input">
              <label>Workout Distance</label>
              <input
                className="textInput"
                value={helpers.getWorkoutDistance(bars)}
                disabled
              />
            </div>
          )}
          {sportType === "bike" && (
            <div className="form-input">
              <label title="Training Load">Training Load</label>
              <input
                className="textInput"
                value={helpers.getStressScore(bars, ftp)}
                disabled
              />
            </div>
          )}
          {sportType === "run" && (
            <LeftRightToggle<"time", "distance">
              label="Duration Type"
              leftValue="time"
              rightValue="distance"
              leftIcon={faClock}
              rightIcon={faRuler}
              selected={durationType}
              onChange={setDurationType}
            />
          )}
          <LeftRightToggle<"bike", "run">
            label="Sport Type"
            leftValue="bike"
            rightValue="run"
            leftIcon={faBiking}
            rightIcon={faRunning}
            selected={sportType}
            onChange={switchSportType}
          />
        </div>
      </div>
      {sportType === "run" && (
        <RunningTimesEditor times={runningTimes} onChange={setRunningTimes} />
      )}
      {textEditorIsVisible && sportType === "bike" && (
        <div className="text-editor">
          <textarea
            onChange={(e) => transformTextToWorkout(e.target.value)}
            rows={10}
            spellCheck={false}
            className="text-editor-textarea"
            placeholder="Add one block per line here: &#10;steady 3.0wkg 30s"
          ></textarea>
          <div className="text-editor-instructions">
            <h2>Instructions</h2>
            <p>
              Every row correspond to a workout block. Scroll down to see some
              examples.
            </p>
            <h3>Blocks</h3>
            <p>
              <span>steady</span> <span>warmup</span> <span>cooldown</span>{" "}
              <span>ramp</span> <span>intervals</span> <span>freeride</span>{" "}
              <span>message</span>
            </p>
            <h3>Time</h3>
            <p>
              <span>30s</span> or <span>0:30m</span>
            </p>
            <h3>Power</h3>
            <p>
              <span>250w</span> or <span>3.0wkg</span> or <span>75%</span> (FTP)
            </p>
            <h3>Cadence</h3>
            <p>
              <span>120rpm</span>
            </p>
            <h2>Examples</h2>
            <h3>Steady block</h3>
            <p>
              <code>steady 3.0wkg 30s</code>
              <code>steady 120w 10m 85rpm</code>
            </p>
            <h3>Warmup / Cooldown / Ramp block</h3>
            <p>
              <code>warmup 2.0wkg-3.5wkg 10m</code>
              <code>cooldown 180w-100w 5m 110rpm</code>
            </p>
            <h3>Intervals</h3>
            <p>
              <code>interval 10x 30s-30s 4.0wkg-1.0wkg 110rpm-85rpm</code>
              <code>interval 3x 1:00m-5:00m 300w-180w</code>
            </p>
            <h3>Free Ride</h3>
            <p>
              <code>freeride 10m 85rpm</code>
            </p>
            <h3>Text Event</h3>
            <p>
              <code>message "Get ready to your first set!" 30s</code>
              <code>message "Last one!" 20:00m</code>
            </p>
          </div>
        </div>
      )}
      <div id="editor" className="editor">
        {actionId && (
          <div className="actions">
            <button onClick={() => moveLeft(actionId)} title="Move Left">
              <FontAwesomeIcon icon={faArrowLeft} size="lg" fixedWidth />
            </button>
            <button onClick={() => moveRight(actionId)} title="Move Right">
              <FontAwesomeIcon icon={faArrowRight} size="lg" fixedWidth />
            </button>
            <button onClick={() => removeBar(actionId)} title="Delete">
              <FontAwesomeIcon icon={faTrash} size="lg" fixedWidth />
            </button>
            <button onClick={() => duplicateBar(actionId)} title="Duplicate">
              <FontAwesomeIcon icon={faCopy} size="lg" fixedWidth />
            </button>
            {sportType === "run" && (
              <select
                name="pace"
                value={getPace(actionId)}
                onChange={(e) => setPace(e.target?.value, actionId)}
                className="selectInput"
              >
                <option value="0">1 Mile Pace</option>
                <option value="1">5K Pace</option>
                <option value="2">10K Pace</option>
                <option value="3">Half Marathon Pace</option>
                <option value="4">Marathon Pace</option>
              </select>
            )}
          </div>
        )}
        <div className="canvas" ref={canvasRef}>
          {actionId && (
            <div
              className="fader"
              style={{ width: canvasRef.current?.scrollWidth }}
              onClick={() => setActionId(undefined)}
            ></div>
          )}
          <div className="segments" ref={segmentsRef}>
            {bars.map((bar) => {
              if (bar.type === "bar") {
                return renderBar(bar);
              } else if (bar.type === "trapeze") {
                return renderTrapeze(bar);
              } else if (bar.type === "freeRide") {
                return renderFreeRide(bar);
              } else if (bar.type === "interval") {
                return renderInterval(bar);
              } else {
                return false;
              }
            })}
          </div>

          <div className="slider">
            {instructions.map((instruction, index) =>
              renderComment(instruction, index)
            )}
          </div>

          {durationType === "time" ? (
            <TimeAxis width={segmentsWidth} />
          ) : (
            <DistanceAxis width={segmentsWidth} />
          )}
        </div>

        <ZoneAxis />
      </div>
      <div className="cta">
        {sportType === "bike" ? (
          <div>
            <ReactTooltip effect="solid" />
            <button
              className="btn btn-square"
              onClick={() => toggleTextEditor()}
              style={{ backgroundColor: "palevioletred" }}
              data-tip="New! Workout text editor!"
            >
              <FontAwesomeIcon icon={faPen} fixedWidth />
            </button>
            <button
              className="btn btn-square"
              onClick={() => addBar(0.5)}
              style={{ backgroundColor: Colors.GRAY }}
            >
              Z1
            </button>
            <button
              className="btn btn-square"
              onClick={() => addBar(Zones.Z2.min)}
              style={{ backgroundColor: Colors.BLUE }}
            >
              Z2
            </button>
            <button
              className="btn btn-square"
              onClick={() => addBar(Zones.Z3.min)}
              style={{ backgroundColor: Colors.GREEN }}
            >
              Z3
            </button>
            <button
              className="btn btn-square"
              onClick={() => addBar(Zones.Z4.min)}
              style={{ backgroundColor: Colors.YELLOW }}
            >
              Z4
            </button>
            <button
              className="btn btn-square"
              onClick={() => addBar(Zones.Z5.min)}
              style={{ backgroundColor: Colors.ORANGE }}
            >
              Z5
            </button>
            <button
              className="btn btn-square"
              onClick={() => addBar(Zones.Z6.min)}
              style={{ backgroundColor: Colors.RED }}
            >
              Z6
            </button>
          </div>
        ) : (
          <button
            className="btn"
            onClick={() => addBar(1, 300, 0, 0, 1000)}
            style={{ backgroundColor: Colors.WHITE }}
          >
            <SteadyLogo className="btn-icon" /> Steady Pace
          </button>
        )}

        <button
          className="btn"
          onClick={() => addTrapeze(0.25, 0.75)}
          style={{ backgroundColor: Colors.WHITE }}
        >
          <WarmupLogo className="btn-icon" /> Warm up
        </button>
        <button
          className="btn"
          onClick={() => addTrapeze(0.75, 0.25)}
          style={{ backgroundColor: Colors.WHITE }}
        >
          <WarmdownLogo className="btn-icon" /> Cool down
        </button>
        <button
          className="btn"
          onClick={() => addInterval()}
          style={{ backgroundColor: Colors.WHITE }}
        >
          <IntervalLogo className="btn-icon" /> Interval
        </button>
        <button
          className="btn"
          onClick={() => addFreeRide()}
          style={{ backgroundColor: Colors.WHITE }}
        >
          <FontAwesomeIcon
            icon={sportType === "bike" ? faBicycle : faRunning}
            size="lg"
            fixedWidth
          />{" "}
          Free {sportType === "bike" ? "Ride" : "Run"}
        </button>
        <button
          className="btn"
          onClick={() => addInstruction()}
          style={{ backgroundColor: Colors.WHITE }}
        >
          <FontAwesomeIcon icon={faComment} size="lg" fixedWidth /> Text Event
        </button>
        {sportType === "bike" && (
          <div className="form-input">
            <label htmlFor="ftp">FTP (W)</label>
            <input
              className="textInput"
              type="number"
              name="ftp"
              value={ftp}
              onChange={(e) => setFtp(parseInt(e.target.value))}
            />
          </div>
        )}

        {sportType === "bike" && (
          <div className="form-input">
            <label htmlFor="weight">Body Weight (Kg)</label>
            <input
              className="textInput"
              type="number"
              name="weight"
              value={weight}
              onChange={(e) => setWeight(parseInt(e.target.value))}
            />
          </div>
        )}

        <button
          className="btn"
          onClick={() => {
            if (
              window.confirm("Are you sure you want to create a new workout?")
            )
              newWorkout();
          }}
        >
          <FontAwesomeIcon icon={faFile} size="lg" fixedWidth /> New
        </button>
        <button className="btn" onClick={() => saveWorkout()}>
          <FontAwesomeIcon icon={faSave} size="lg" fixedWidth /> Save
        </button>
        <button
          className="btn"
          onClick={() => {
            if (window.confirm("Are you sure you want to delete this workout?"))
              deleteWorkout();
          }}
        >
          <FontAwesomeIcon icon={faTrash} size="lg" fixedWidth /> Delete
        </button>
        <button className="btn" onClick={() => downloadWorkout()}>
          <FontAwesomeIcon icon={faDownload} size="lg" fixedWidth /> Download
        </button>
        <input
          accept=".xml,.zwo"
          id="contained-button-file"
          type="file"
          style={{ display: "none" }}
          onChange={(e) => handleUpload(e.target.files![0])}
        />
        <button
          className="btn"
          onClick={() =>
            document.getElementById("contained-button-file")!.click()
          }
        >
          <FontAwesomeIcon icon={faUpload} size="lg" fixedWidth /> Upload
        </button>
        <button className="btn" onClick={() => setShowWorkouts(true)}>
          <FontAwesomeIcon icon={faList} size="lg" fixedWidth /> Workouts
        </button>
        <button className="btn" onClick={() => shareWorkout()}>
          <FontAwesomeIcon icon={faShareAlt} size="lg" fixedWidth /> Share
        </button>
      </div>
      <Footer />
    </div>
  );
}