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

The following examples show how to use @fortawesome/free-solid-svg-icons#faExclamationCircle. 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: request-result.tsx    From example with MIT License 6 votes vote down vote up
export function RequestResult({ result, completeRender }: IRequestResultProps<any>) {
	switch (result.type) {
		case "empty":
			return null
		case "error":
			return <Alert severity="error" icon={<Icon icon={faExclamationCircle}/>}>
				<AlertTitle>Request rejected</AlertTitle>
				{result.error}
			</Alert>
		case "complete":
			return <Box>
				<Alert variant="outlined" severity="success" icon={<Icon icon={faCheck}/>}>
					<AlertTitle>Request completed</AlertTitle>
					{completeRender?.(result.data)}
				</Alert>
			</Box>
	}
}
Example #2
Source File: header.component.ts    From thorchain-explorer-singlechain with MIT License 6 votes vote down vote up
constructor(
    private networkService: NetworkService,
    private uiStyleToggleService: UiStyleToggleService,
    private thorchainNetworkService: ThorchainNetworkService) {
      this.optimalIcon = faCheckCircle;
      this.warningIcon = faExclamationCircle;
      this.alertIcon = faExclamationTriangle;
      this.theme = localStorage.getItem('THEME');

      const network$ = this.thorchainNetworkService.networkUpdated$.subscribe(
        (_) => {
          this.getNetworkStatus();
        }
      );

      this.subs = [network$];

  }
Example #3
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 #4
Source File: LoadingIcon.tsx    From solo with MIT License 6 votes vote down vote up
LoadingIcon: React.FC<LoadingStatus> = ({ loading, error }) => (
  <span>
    <FontAwesomeIcon
      spin={loading}
      icon={loading ? faSpinner : error ? faExclamationCircle : faCheck}
      className={classNames({
        [classes.error]: error
      })}
    />
  </span>
)
Example #5
Source File: InsertTuple.tsx    From datajoint-labbook with MIT License 5 votes vote down vote up
render() {
    return (
      <div>
        <form onSubmit={this.onSubmit}>
          <div className="inputRow">
            {
              // Deal with primary attirbutes
              this.props.tableAttributesInfo?.primaryAttributes.map((primaryTableAttribute) => {
                return(
                  <div className='fieldUnit' key={primaryTableAttribute.attributeName}>
                    {PrimaryTableAttribute.getAttributeLabelBlock(primaryTableAttribute)}
                    {PrimaryTableAttribute.getPrimaryAttributeInputBlock(primaryTableAttribute, this.state.tupleBuffer[primaryTableAttribute.attributeName], this.handleChange)}
                  </div>
                )
              })
            }
            {
              // Deal with secondary attributes 
              this.props.tableAttributesInfo?.secondaryAttributes.map((secondaryAttribute) => {
                return(
                  <div className='fieldUnit' key={secondaryAttribute.attributeName}>
                    {SecondaryTableAttribute.getAttributeLabelBlock(secondaryAttribute, this.resetToNull)}
                    {SecondaryTableAttribute.getSecondaryAttributeInputBlock(
                      secondaryAttribute,
                      secondaryAttribute.attributeType === TableAttributeType.DATETIME || secondaryAttribute.attributeType === TableAttributeType.TIMESTAMP?  
                      this.state.tupleBuffer[secondaryAttribute.attributeName + '__date'] + ' ' + this.state.tupleBuffer[secondaryAttribute.attributeName + '__time'] :
                        this.state.tupleBuffer[secondaryAttribute.attributeName], 
                      this.handleChange)}
                  </div>
                )
              })
            }
          </div>
          {
            this.props.selectedTableEntry !== undefined ?
            <div className="copyOverPrompt">
              <FontAwesomeIcon className="icon" icon={faExclamationCircle}/>
              <span>Table entry selection detected. Copy over for a quick prefill?</span>
              <button onClick={(event) => this.copyTuple(event)}>Copy Over</button>
            </div> :
            ''
          } 
          <input className="confirmActionButton insert" type='submit' value='Insert'></input>
        </form>
        {this.state.errorMessage ? (
          <div>{this.state.errorMessage}<button className="dismiss" onClick={() => this.setState({errorMessage: ''})}>dismiss</button></div>
        ) : ''}
      </div>
    )
  }
Example #6
Source File: fa-library.ts    From eth2stats-dashboard with MIT License 5 votes vote down vote up
library.add(faBell, faChevronDown, faTimes, faArrowRight, faCheck, faPlusCircle,
    faExclamationCircle, faHeart, faCodeBranch, faMap, faList, faCircle,
    faDotCircle,
    faCheckCircle, faNetworkWired, faUsers, faCube, faSortUp, faSortDown,
    faEllipsisV, faSync, faMicrochip, faCheckDouble, faLaptopCode);
Example #7
Source File: Wallet.tsx    From argo-react with MIT License 4 votes vote down vote up
Wallet = () => {
  const history = useHistory();
  const { userLoading, selectedOrg, orgLoading } =
    useContext<IStateModel>(StateContext);
  const { fetchUser } = useContext<IActionModel>(ActionContext);
  const [paymentsLoading, setPaymentsLoading] = useState<boolean>(false);
  const [walletLoading, setWalletLoading] = useState<boolean>(false);
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  const [payments, setPayments] = useState<IPaymentModel[]>([]);
  const [orgWallet, setOrgWallet] = useState<string>("");
  const [wallet, setWallet] = useState<string>("");
  const [walletBal, setWalletBal] = useState<number>(0);
  const [argoAllowance, setArgoAllowance] = useState<number>(-1);
  const [walletLoader, setWalletLoader] = useState<boolean>(false);
  const [enableLoader, setEnableLoader] = useState<boolean>(false);
  const [removalLoader, setRemovalLoader] = useState<boolean>(false);
  const [errorWarning, setErrorWarning] = useState<boolean>(false);
  const [errorMessage, setErrorMessage] = useState<string>("");

  const componentIsMounted = useRef(true);

  const isWalletPresent = !!selectedOrg?.wallet;

  useEffect(() => {
    if (selectedOrg && !orgLoading) {
      setPaymentsLoading(true);
      setWalletLoading(true);
      // eslint-disable-next-line react-hooks/exhaustive-deps
      if (componentIsMounted.current) {
        setOrgWallet(selectedOrg.wallet ? selectedOrg.wallet.address : "");
        setWalletLoading(false);
        setPayments(selectedOrg.payments || []);
        setPaymentsLoading(false);
      }
    } else {
      if (orgLoading) {
        setPaymentsLoading(true);
        setWalletLoading(true);
      } else {
        setPaymentsLoading(false);
        setWalletLoading(false);
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [selectedOrg, orgLoading]);

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

  const connectWallet = async () => {
    setWalletLoader(true);
    try {
      const wallet = await Web3Service.getPolygonAccount();
      setWallet(wallet);
      let walletBal = 0;
      walletBal = await Web3Service.getArgoBalance(wallet);

      setWalletBal(walletBal);
      setWalletLoader(false);
    } catch (err) {
      setErrorMessage((err as any).message);
      setErrorWarning(true);
      setTimeout(() => {
        setErrorWarning(false);
        setErrorMessage("");
      }, 5000);
      setWalletLoader(false);
      // eslint-disable-next-line no-console
      console.log(err);
    }
  };

  const checkAllowance = async () => {
    setWalletLoader(true);
    try {
      await Web3Service.getPolygonAccount();
      let walletApproval = 0;
      walletApproval = await Web3Service.getArgoAllowances(orgWallet);
      setArgoAllowance(walletApproval);
      setWalletLoader(false);
    } catch (err) {
      setErrorMessage((err as any).message);
      setErrorWarning(true);
      setTimeout(() => {
        setErrorWarning(false);
        setErrorMessage("");
      }, 5000);
      setWalletLoader(false);
      // eslint-disable-next-line no-console
      console.log(err);
    }
  };

  const enableWallet = async () => {
    setEnableLoader(true);
    const walletBody = {
      address: wallet,
      orgId: selectedOrg?._id,
    };
    ApiService.enableWallet(walletBody).subscribe(
      (res) => {
        if (componentIsMounted.current) {
          setEnableLoader(false);
          fetchUser();
        }
      },
      (err) => {
        setErrorMessage((err as any).message);
        setErrorWarning(true);
        setTimeout(() => {
          setErrorWarning(false);
          setErrorMessage("");
        }, 5000);
      },
    );
  };

  const removeWallet = async () => {
    setRemovalLoader(true);
    try {
      await Web3Service.getPolygonAccount();
      const signature = await Web3Service.signRemoveWallet();
      const removeBody = {
        id: selectedOrg?.wallet._id,
        signature,
      };
      ApiService.removeWallet(removeBody).subscribe(
        (res) => {
          if (componentIsMounted.current) {
            setRemovalLoader(false);
            fetchUser();
          }
        },
        (err) => {
          setErrorMessage((err as any).message);
          setErrorWarning(true);
          setTimeout(() => {
            setErrorWarning(false);
            setErrorMessage("");
          }, 5000);
        },
      );
    } catch (err) {
      setErrorMessage((err as any).message);
      setErrorWarning(true);
      setTimeout(() => {
        setErrorWarning(false);
        setErrorMessage("");
      }, 5000);
      setRemovalLoader(false);
      // eslint-disable-next-line no-console
      console.log(err);
    }
  };
  const showProtocolPrice = (protocol: string) => {
    switch (protocol) {
      case "arweave":
        return "AR";
      case "skynet":
        return "SC";
      case "neofs":
        return "NEO";
      case "ipfs-filecoin":
        return "FIL";
      case "ipfs-pinata":
        return "USD";

      default:
    }
  };

  return (
    <div className="Wallet">
      {errorWarning ? (
        <div className="warning-container">
          <div className="warning-header">
            <FontAwesomeIcon icon={faExclamationCircle} /> {errorMessage}
          </div>
        </div>
      ) : null}
      <div className="wallet-container">
        <div className="wallet-details">
          <div className="wallet-header">
            <span>Organisation Wallet</span>
          </div>
          <div className="wallet-body">
            {!isWalletPresent && !walletLoading ? (
              <>
                <div className="wallet-subtitle">
                  Enable your wallet for <b>{selectedOrg?.profile.name}</b>
                </div>
                <div className="wallet-info">
                  <FontAwesomeIcon
                    icon={faInfoCircle}
                    style={{ marginRight: 7 }}
                  ></FontAwesomeIcon>
                  We currently support Matic Mumbai Testnet. Please add Matic Mumbai
                  chain in your metamask.
                </div>
                {!wallet ? (
                  <button
                    type="button"
                    className="primary-button"
                    disabled={userLoading}
                    onClick={connectWallet}
                  >
                    Connect
                  </button>
                ) : (
                  <div className="wallet-recharge-form">
                    <label className="wallet-recharge-form-title">
                      Wallet Details
                    </label>
                    <div className="wallet-details-container">
                      <div className="wallet-details-items">
                        <div className="wallet-details-item-title">
                          Wallet Address
                        </div>
                        <div className="wallet-details-item-desc">
                          {!walletLoader ? (
                            wallet
                          ) : (
                            <Skeleton width={300} duration={2} />
                          )}
                        </div>
                      </div>
                      <div className="wallet-details-items">
                        <div className="wallet-details-item-title">ARGO Balance</div>
                        <div className="wallet-details-item-desc">
                          {!walletLoader ? (
                            `${walletBal} $ARGO`
                          ) : (
                            <Skeleton width={150} duration={2} />
                          )}
                        </div>
                      </div>
                    </div>
                    <div className="wallet-details-button">
                      <button
                        type="button"
                        className="primary-button"
                        disabled={enableLoader}
                        onClick={enableWallet}
                      >
                        {enableLoader && (
                          <BounceLoader size={20} color={"#fff"} loading={true} />
                        )}
                        Save
                      </button>
                    </div>
                  </div>
                )}
              </>
            ) : (
              <>
                <div className="wallet-body-header">
                  <div>
                    <div className="wallet-body-title">Wallet Details</div>
                    <div className="wallet-note">
                      Note: Only owner of this wallet can increase allowance
                    </div>
                  </div>
                  <div className="button-container">
                    {!walletLoading && (
                      <>
                        <button
                          type="button"
                          className="primary-button remove-button"
                          disabled={walletLoading}
                          onClick={removeWallet}
                        >
                          {removalLoader && (
                            <BounceLoader size={20} color={"#fff"} loading={true} />
                          )}
                          Remove Wallet
                        </button>
                        <button
                          type="button"
                          className="primary-button"
                          disabled={walletLoading}
                          onClick={() => history.push("/wallet/recharge")}
                        >
                          Set Allowance
                        </button>
                      </>
                    )}
                  </div>
                </div>
                <div className="wallet-details-body">
                  <div className="wallet-details-item">
                    <label>Address</label>
                    <span>
                      {!walletLoading ? (
                        `${orgWallet}`
                      ) : (
                        <Skeleton width={150} duration={2} />
                      )}
                    </span>
                  </div>
                  <div className="wallet-details-item">
                    <label>Allowance</label>
                    <span>
                      {!walletLoading ? (
                        <div>
                          {argoAllowance === -1 ? (
                            <button
                              type="button"
                              className="primary-button"
                              disabled={walletLoader}
                              onClick={checkAllowance}
                            >
                              {walletLoader && (
                                <BounceLoader
                                  size={20}
                                  color={"#fff"}
                                  loading={true}
                                />
                              )}
                              Check Allowance
                            </button>
                          ) : (
                            `${argoAllowance} $ARGO`
                          )}
                        </div>
                      ) : (
                        <Skeleton width={150} duration={2} />
                      )}
                    </span>
                  </div>
                </div>
              </>
            )}
          </div>
        </div>
        <div className="payment-details">
          <div className="payment-header">
            <span>Payments</span>
          </div>
          <div className="payment-body">
            <div className="table">
              <div className="thead">
                <div className="tr">
                  <div className="th">Project Name</div>
                  <div className="th">Deployment Id</div>
                  <div className="th">Build Time</div>
                  <div className="th">Upload Fee</div>
                  <div className="th">Amount</div>
                  <div className="th">Date</div>
                </div>
              </div>
              {!paymentsLoading ? (
                <div className="tbody">
                  <ReactTooltip delayShow={50} />
                  {payments.length > 0 ? (
                    payments.map((payment: IPaymentModel, index: number) => (
                      <div className="tr" key={index}>
                        <div className="td">
                          <div className="user-container">
                            <div className="user-text">
                              <span
                                className="tooltip"
                                data-tip={payment?.projectName}
                              >
                                {payment?.projectName}
                              </span>
                            </div>
                          </div>
                        </div>
                        <div className="td">
                          <div className="user-container">
                            <div className="user-text">
                              <span
                                className="tooltip"
                                data-tip={payment?.deploymentId}
                              >
                                {payment?.deploymentId}
                              </span>
                            </div>
                          </div>
                        </div>
                        <div className="td">
                          <div className="user-container">
                            <div className="user-text">{payment?.buildTime} s</div>
                          </div>
                        </div>
                        <div className="td">
                          <div className="user-container">
                            <div className="user-text">
                              <span
                                className="tooltip"
                                data-tip={`${
                                  payment?.providerFee
                                } ${showProtocolPrice(payment?.protocol)}`}
                              >
                                {payment?.providerFee.toFixed(5)}{" "}
                                {showProtocolPrice(payment?.protocol)}
                              </span>
                            </div>
                          </div>
                        </div>
                        <div className="td">
                          <div className="user-container">
                            <div className="user-text">
                              <span
                                className="tooltip"
                                data-tip={`${payment?.finalArgoFee} $${payment.token}`}
                              >
                                {payment?.finalArgoFee.toFixed(3)} ${payment.token}
                              </span>
                            </div>
                          </div>
                        </div>
                        <div className="td">
                          <div className="user-container">
                            <div className="user-text">
                              {moment(payment?.createdAt).format(
                                "DD-MM-YYYY hh:mm A",
                              )}
                            </div>
                          </div>
                        </div>
                      </div>
                    ))
                  ) : (
                    <div className="tr tr-center">No payments to show</div>
                  )}
                </div>
              ) : (
                <div className="tbody">
                  <div className="tr">
                    <div className="td">
                      <div className="user-container">
                        <div className="user-text">
                          <Skeleton width={80} duration={2} />
                        </div>
                      </div>
                    </div>
                    <div className="td">
                      <div className="user-container">
                        <div className="user-text">
                          <Skeleton width={90} duration={2} />
                        </div>
                      </div>
                    </div>
                    <div className="td">
                      <div className="user-container">
                        <div className="user-text">
                          <Skeleton width={50} duration={2} />
                        </div>
                      </div>
                    </div>
                    <div className="td">
                      <div className="user-container">
                        <div className="user-text">
                          <Skeleton width={50} duration={2} />
                        </div>
                      </div>
                    </div>
                    <div className="td">
                      <div className="user-container">
                        <div className="user-text">
                          <Skeleton width={50} duration={2} />
                        </div>
                      </div>
                    </div>
                    <div className="td">
                      <div className="user-container">
                        <div className="user-text">
                          <Skeleton width={80} duration={2} />
                        </div>
                      </div>
                    </div>
                  </div>
                  <div className="tr">
                    <div className="td">
                      <div className="user-container">
                        <div className="user-text">
                          <Skeleton width={80} duration={2} />
                        </div>
                      </div>
                    </div>
                    <div className="td">
                      <div className="user-container">
                        <div className="user-text">
                          <Skeleton width={90} duration={2} />
                        </div>
                      </div>
                    </div>
                    <div className="td">
                      <div className="user-container">
                        <div className="user-text">
                          <Skeleton width={50} duration={2} />
                        </div>
                      </div>
                    </div>
                    <div className="td">
                      <div className="user-container">
                        <div className="user-text">
                          <Skeleton width={50} duration={2} />
                        </div>
                      </div>
                    </div>
                    <div className="td">
                      <div className="user-container">
                        <div className="user-text">
                          <Skeleton width={50} duration={2} />
                        </div>
                      </div>
                    </div>
                    <div className="td">
                      <div className="user-container">
                        <div className="user-text">
                          <Skeleton width={80} duration={2} />
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
              )}
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}
Example #8
Source File: WalletRecharge.tsx    From argo-react with MIT License 4 votes vote down vote up
function WalletRecharge() {
  const history = useHistory();
  const { fetchUser } = useContext<IActionModel>(ActionContext);
  const { selectedOrg, orgLoading } = useContext<IStateModel>(StateContext);

  const [wallet, setWallet] = useState<string>("");
  const [walletBal, setWalletBal] = useState<number>(0);
  const [walletApproval, setWalletApproval] = useState<number>(0);
  const [rechargeAmount, setRechargeAmount] = useState<string>("");
  const [walletLoader, setWalletLoader] = useState<boolean>(false);
  const [rechargeLoader, setRechargeLoader] = useState<boolean>(false);
  const [walletLoading, setWalletLoading] = useState<boolean>(false);
  const [orgWallet, setOrgWallet] = useState<string>("");
  const [errorWarning, setErrorWarning] = useState<boolean>(false);
  const [errorMessage, setErrorMessage] = useState<string>("");
  const componentIsMounted = useRef(true);

  useEffect(() => {
    if (selectedOrg && !orgLoading) {
      setWalletLoading(true);
      if (componentIsMounted.current) {
        setOrgWallet(selectedOrg.wallet.address);
        setWalletLoading(false);
      }
    } else {
      if (orgLoading) {
        setWalletLoading(true);
      } else {
        setWalletLoading(false);
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [selectedOrg, orgLoading]);

  const rechargeArGo = async () => {
    setErrorWarning(false);
    try {
      if (!wallet) {
        setWalletLoader(true);
        const wallet = await Web3Service.getPolygonAccount();
        setWallet(wallet);
        if (wallet) {
          const walletBal = await Web3Service.getArgoBalance(wallet);
          const walletApproval = await Web3Service.getArgoAllowances(wallet);
          setWalletBal(walletBal);
          setWalletApproval(walletApproval);
        }
        setWalletLoader(false);
      } else {
        setRechargeLoader(true);
        await Web3Service.giveAllowance(rechargeAmount);
        setRechargeLoader(false);
        fetchUser();
        history.goBack();
      }
    } catch (err) {
      // eslint-disable-next-line no-console
      console.log(err);
      setWalletLoader(false);
      setRechargeLoader(false);
      setErrorMessage((err as any).message);
      setErrorWarning(true);
      setTimeout(() => {
        setErrorWarning(false);
        setErrorMessage("");
      }, 5000);
      // window.location.reload();
    }
  };

  const refreshWallet = async () => {
    try {
      setErrorWarning(false);
      setWalletLoader(true);
      const wallet = await Web3Service.getPolygonCurrentAccount();
      const walletBal = await Web3Service.getArgoBalance(wallet);
      const walletApproval = await Web3Service.getArgoAllowances(wallet);
      setWallet(wallet);
      setWalletBal(walletBal);
      setWalletApproval(walletApproval);
      setWalletLoader(false);
    } catch (err) {
      // eslint-disable-next-line no-console
      console.log(err);
      setWalletLoader(false);
      setErrorMessage((err as any).message);
      setErrorWarning(true);
      setTimeout(() => {
        setErrorWarning(false);
        setErrorMessage("");
      }, 5000);
      // window.location.reload();
    }
  };

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

  const rechargeDisable =
    rechargeLoader || wallet ? !rechargeAmount || wallet !== orgWallet : false;

  return (
    <div className="WalletRecharge">
      <RootHeader parent={"CreateOrg"} />
      <main className="app-main">
        <div className="wallet-recharge-container">
          <div className="wallet-recharge-card">
            <div className="wallet-recharge-card-inner">
              <h1 className="wallet-recharge-title">Set Allowance</h1>
              <div className="wallet-recharge-form">
                <label className="wallet-recharge-form-title">Your wallet</label>
                <label className="wallet-chain-info">
                  <FontAwesomeIcon
                    icon={faInfoCircle}
                    style={{ marginRight: 7 }}
                  ></FontAwesomeIcon>
                  We currently support Matic Mumbai Testnet. Please add Matic Mumbai
                  chain in your metamask.
                </label>
                <label className="wallet-recharge-form-subtitle">
                  Please approve more than minimum $ARGO tokens to our Payment Smart
                  Contract. Approval transaction is <b>Gassless</b>, no need to hold
                  $MATIC tokens for approval.
                </label>
                <label className="wallet-recharge-form-subtitle">
                  To start deploying your application, minimum allowance required is
                  60 $ARGO and minimum balance required is 60 $ARGO tokens.
                </label>
                <label className="wallet-recharge-form-subtitle">
                  To get <b>Matic Testnet $ARGO Tokens</b>, please visit{" "}
                  <a
                    href="https://faucet.spheron.network/"
                    target="_blank"
                    rel="noopener noreferrer"
                  >
                    https://faucet.spheron.network
                  </a>
                  .
                </label>
                <div className="current-wallet-details">
                  <div className="current-wallet-details-title">Owner Address:</div>
                  <div className="current-wallet-details-desc">
                    {!walletLoading ? (
                      `${orgWallet}`
                    ) : (
                      <Skeleton width={150} duration={2} />
                    )}
                  </div>
                </div>
              </div>
              {wallet && (
                <>
                  <div className="wallet-recharge-form">
                    <div className="wallet-recharge-form-title-container">
                      <label className="wallet-recharge-form-title">
                        Wallet Details
                      </label>
                      <div className="refresh-control" onClick={refreshWallet}>
                        <FontAwesomeIcon icon={faSyncAlt}></FontAwesomeIcon>
                      </div>
                    </div>
                    <div className="wallet-details-container">
                      <div className="wallet-details-items">
                        <div className="wallet-details-item-title">
                          Wallet Address
                        </div>
                        <div className="wallet-details-item-desc">
                          {!walletLoader ? (
                            wallet
                          ) : (
                            <Skeleton width={300} duration={2} />
                          )}
                        </div>
                      </div>
                      <div className="wallet-details-items">
                        <div className="wallet-details-item-title">ARGO Balance</div>
                        <div className="wallet-details-item-desc">
                          {!walletLoader ? (
                            `${walletBal} $ARGO`
                          ) : (
                            <Skeleton width={150} duration={2} />
                          )}
                        </div>
                      </div>
                      <div className="wallet-details-items">
                        <div className="wallet-details-item-title">
                          ARGO Allowance
                        </div>
                        <div className="wallet-details-item-desc">
                          {!walletLoader ? (
                            `${walletApproval} $ARGO`
                          ) : (
                            <Skeleton width={150} duration={2} />
                          )}
                        </div>
                      </div>
                    </div>
                  </div>
                  <div className="wallet-recharge-form">
                    <label className="wallet-recharge-form-title">
                      Approval Amount
                    </label>
                    <label className="wallet-recharge-form-subtitle">
                      Please provide the approval amount.
                    </label>
                    <input
                      type="number"
                      className="wallet-recharge-form-input"
                      value={rechargeAmount}
                      onChange={(e) => setRechargeAmount(e.target.value)}
                    />
                  </div>
                </>
              )}
              {wallet && wallet !== orgWallet ? (
                <div className="note-container">
                  Note: We currently support Matic Mumbai Testnet. Only owner of this
                  wallet can increase allowance
                </div>
              ) : null}
              <div className="button-container">
                <button
                  type="button"
                  className="primary-button"
                  disabled={rechargeDisable}
                  onClick={rechargeArGo}
                >
                  {rechargeLoader && (
                    <BounceLoader size={20} color={"#fff"} loading={true} />
                  )}
                  {!wallet ? "Connect" : "Approve"}
                </button>
                <button
                  type="button"
                  className="cancel-button"
                  onClick={(e) => history.goBack()}
                >
                  Cancel
                </button>
              </div>
              {errorWarning ? (
                <div className="warning-container">
                  <div className="warning-header">
                    <FontAwesomeIcon icon={faExclamationCircle} /> {errorMessage}
                  </div>
                </div>
              ) : null}
            </div>
          </div>
        </div>
      </main>
    </div>
  );
}
Example #9
Source File: SettingsGeneral.tsx    From argo-react with MIT License 4 votes vote down vote up
SettingsGeneral = () => {
  // const history = useHistory();

  const { user, userLoading } = useContext(StateContext);
  const { fetchUser } = useContext(ActionContext);

  const [username, setUsername] = useState<string>("");
  const [name, setName] = useState<string>("");
  const [email, setEmail] = useState<string | undefined>(undefined);
  const [avatar, setAvatar] = useState<string>("");
  const [isDataChanged, setIsDataChanged] = useState<boolean>(false);
  // const [deleteConfirmed, setDeleteConfirmed] = useState<boolean>(false);
  const [updateLoading, setUpdateLoading] = useState<boolean>(false);
  // const [deleteLoading, setDeleteLoading] = useState<boolean>(false);

  useEffect(() => {
    if (user) {
      setUsername(user.argoProfile.username);
      setName(user.argoProfile.name);
      setEmail(user.argoProfile.email ? user.argoProfile.email : undefined);
      setAvatar(user.argoProfile.avatar);
    }
  }, [user]);

  useEffect(() => {
    if (user) {
      if (
        user.argoProfile.username !== username ||
        user.argoProfile.name !== name ||
        user.argoProfile.avatar !== avatar
      ) {
        setIsDataChanged(true);
      } else {
        setIsDataChanged(false);
      }
    }
  }, [avatar, name, user, username]);

  const fileUpload = (file: any) => {
    const reader: FileReader = new window.FileReader();
    reader.readAsDataURL(file);
    reader.onloadend = () => saveURL(reader);
  };

  const saveURL = async (reader: FileReader) => {
    setAvatar(`${reader.result}`);
  };

  const updateProfile = () => {
    setUpdateLoading(true);
    const profile = {
      ...user?.argoProfile,
      username,
      name,
      avatar,
    };

    ApiService.updateProfile(profile).subscribe((result) => {
      setUpdateLoading(false);
      fetchUser();
    });
  };

  // const deleteUser = () => {
  //   if (user && deleteConfirmed) {
  //     setDeleteLoading(true);

  //     ApiService.deleteProfile((user as any)._id).subscribe((result) => {
  //       setDeleteLoading(false);
  //       localStorage.removeItem("jwt-token");
  //       history.push("/signup");
  //     });
  //   }
  // };
  return (
    <div className="UserSettingsGeneral">
      <div className="settings-right-container">
        <div className="settings-profile-details">
          <div className="settings-profile-header">Profile Details</div>
          <div className="settings-profile-body">
            <div className="settings-profile-item">
              <label className="settings-profile-item-title">Your Username</label>
              <label className="settings-profile-item-subtitle">
                This is your ArGo username taken from OAuth provider.
              </label>
              {!userLoading ? (
                <input
                  type="text"
                  className="settings-profile-item-input"
                  value={username}
                  onChange={(e) => setUsername(e.target.value)}
                />
              ) : (
                <Skeleton width={326} height={36} duration={2} />
              )}
            </div>
            <div className="settings-profile-item">
              <label className="settings-profile-item-title">Your Name</label>
              <label className="settings-profile-item-subtitle">
                Please enter your name, or a display name you are comfortable with.
              </label>
              {!userLoading ? (
                <input
                  type="text"
                  className="settings-profile-item-input"
                  value={name}
                  onChange={(e) => setName(e.target.value)}
                />
              ) : (
                <Skeleton width={326} height={36} duration={2} />
              )}
            </div>
            <div className="settings-profile-item">
              <label className="settings-profile-item-title">Your Email</label>
              <label className="settings-profile-item-subtitle">
                This is your email address connected with the OAuth provider. We
                don't allow it to be edited as of now.
              </label>
              {!userLoading ? (
                <input
                  type="text"
                  className="settings-profile-item-input"
                  value={email}
                  disabled
                />
              ) : (
                <Skeleton width={326} height={36} duration={2} />
              )}
            </div>
            <div className="settings-profile-item avatar-container">
              <div className="settings-profile-item-avatar-container">
                <label className="settings-profile-item-title">Your Avatar</label>
                <label className="settings-profile-item-subtitle">
                  This is your avatar taken from the OAuth provider.
                </label>
                <label className="settings-profile-item-subtitle">
                  Click on the avatar to upload a custom one from your files.
                </label>
              </div>
              <div className="settings-profile-avatar-image-container">
                {!userLoading ? (
                  <>
                    <input
                      type="file"
                      className="file-upload"
                      onChange={(e) =>
                        e.target.files ? fileUpload(e.target.files[0]) : undefined
                      }
                    />
                    <LazyLoadedImage height={64} once>
                      <img
                        src={
                          avatar
                            ? avatar
                            : require("../../../../assets/svg/camera_grad.svg")
                        }
                        alt="avatar"
                        className="settings-avatar"
                        height={64}
                        width={64}
                        loading="lazy"
                      />
                    </LazyLoadedImage>
                  </>
                ) : (
                  <Skeleton circle={true} height={64} width={64} duration={2} />
                )}
              </div>
            </div>
          </div>
          <div className="settings-profile-footer">
            <div className="warning-text-container">
              <span className="exclamation-icon">
                <FontAwesomeIcon icon={faExclamationCircle}></FontAwesomeIcon>
              </span>
              <span>Click save to update your profile</span>
            </div>
            <button
              type="button"
              className="primary-button"
              disabled={userLoading || !isDataChanged}
              onClick={updateProfile}
            >
              {updateLoading && (
                <BounceLoader size={20} color={"#fff"} loading={true} />
              )}
              Save
            </button>
          </div>
        </div>
        {/* <div className="settings-profile-details">
          <div className="settings-profile-header delete-containers">
            Delete Your ArGo Account
          </div>
          <div className="settings-profile-body">
            <div className="delete-org-container">
              This action will queue the removal of all your Vercel account's data,
              including:
              <br /> Deployments, Activity, Aliases, Domains, Certificates and your
              Billing subscription.
            </div>
            <div className="delete-org-confirm-container">
              <span className="confirm-checkbox">
                <input
                  type="checkbox"
                  checked={deleteConfirmed}
                  onChange={(e) => setDeleteConfirmed(!deleteConfirmed)}
                />
              </span>
              <span>
                Confirm that I want to start the account deletion process for the
                account <b>Prashant</b>.
              </span>
            </div>
          </div>
          <div className="settings-profile-footer delete-containers">
            <div className="warning-text-container">
              <span className="exclamation-icon">
                <FontAwesomeIcon icon={faExclamationCircle}></FontAwesomeIcon>
              </span>
              <span>
                Please confirm and click delete button to delete your profile.
              </span>
            </div>
            <button
              type="button"
              className="primary-button delete-button"
              disabled={!deleteConfirmed}
              onClick={deleteUser}
            >
              {deleteLoading && (
                <BounceLoader size={20} color={"#fff"} loading={true} />
              )}
              Delete
            </button>
          </div>
        </div> */}
      </div>
    </div>
  );
}
Example #10
Source File: SettingsGeneral.tsx    From argo-react with MIT License 4 votes vote down vote up
SettingsGeneral = () => {
  const { selectedProject, projectLoading, selectedOrg } =
    useContext<IStateModel>(StateContext);

  const { fetchProject } = useContext<IActionModel>(ActionContext);

  const [workspace, setWorkspace] = useState<string>("");

  const [archiveConfirmed, setArchiveConfirmed] = useState<boolean>(false);
  const [archiveLoading, setArchiveLoading] = useState<boolean>(false);

  const lastPublishedDate = moment(selectedProject?.updatedAt).format(
    "MMM DD, YYYY hh:mm A",
  );

  const lastCreatedDate = moment(selectedProject?.createdAt).format(
    "MMM DD, YYYY hh:mm A",
  );

  const [installationId, setInstallationId] = useState<number>(0);
  const componentIsMounted = useRef(true);

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

  let displayGithubRepo = "";
  if (selectedProject) {
    displayGithubRepo = selectedProject.githubUrl.substring(
      19,
      selectedProject.githubUrl.length - 4,
    );
  }

  useEffect(() => {
    if (selectedProject) {
      setWorkspace(
        selectedProject?.latestDeployment?.configuration.workspace
          ? selectedProject?.latestDeployment?.configuration.workspace
          : "",
      );
      const ownerName = selectedProject?.githubUrl
        .substring(19, selectedProject?.githubUrl.length - 4)
        .split("/")[0];
      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 === ownerName,
            )[0];
            setInstallationId(newRepoOwner.installationId);
          }
        }
      });
    }
  }, [selectedProject]);

  const imageUrl = (imageUrl: string | undefined) => {
    if (imageUrl) {
      return imageUrl;
    }
    return config.urls.IMAGE_NOT_FOUND;
  };

  const projectArchive = () => {
    const body = {
      installationId,
    };
    setArchiveLoading(true);
    ApiService.archiveProject(selectedProject?._id, body).subscribe(
      (result) => {
        setArchiveLoading(false);
        setArchiveConfirmed(false);
        fetchProject(`${selectedProject?._id}`);
      },
      (error) => {
        // eslint-disable-next-line no-console
        console.log(error);
      },
    );
  };

  const projectMaintain = () => {
    const body = {
      installationId,
    };
    setArchiveLoading(true);
    ApiService.maintainProject(selectedProject?._id, body).subscribe(
      (result) => {
        setArchiveLoading(false);
        setArchiveConfirmed(false);
        fetchProject(`${selectedProject?._id}`);
      },
      (error) => {
        // eslint-disable-next-line no-console
        console.log(error);
      },
    );
  };

  // useEffect(() => {
  //   if (selectedProject) {
  //     if (selectedProject?.latestDeployment?.configuration.workspace !== workspace) {
  //       setIsDataChanged(true);
  //     } else {
  //       setIsDataChanged(false);
  //     }
  //   }
  //   // eslint-disable-next-line react-hooks/exhaustive-deps
  // }, [selectedProject, workspace]);

  // const updateProject = () => {
  //   if (selectedProject) {
  //     setUpdateLoading(true);
  //     const project = {
  //       package_manager:
  //         selectedProject?.latestDeployment?.configuration.packageManager,
  //       build_command: selectedProject?.latestDeployment?.configuration.buildCommand,
  //       publish_dir: selectedProject?.latestDeployment?.configuration.publishDir,
  //       branch: selectedProject?.latestDeployment?.configuration.branch,
  //       workspace,
  //     };

  //     ApiService.updateProject(`${selectedProject?._id}`, project).subscribe(
  //       (result) => {
  //         setUpdateLoading(false);
  //         fetchProject(`${selectedProject?._id}`);
  //       },
  //     );
  //   }
  // };

  // const deleteOrg = () => {
  //   if (selectedOrg && deleteConfirmed) {
  //     ApiService.deleteOrganization((selectedOrg as any)._id).subscribe((result) => {
  //       fetchUser();
  //       history.push("/dashboard");
  //     });
  //   }
  // };

  const archiveState =
    selectedProject?.state === "ARCHIVED" ? "unarchive" : "archive";

  return (
    <div className="SettingsGeneral">
      <div className="settings-right-container">
        <div className="settings-project-details">
          <div className="settings-project-header">Project Details</div>
          <div className="settings-project-body">
            {/* <div className="settings-project-item">
              <label className="settings-project-item-title">Project Name</label>
              <label className="settings-project-item-subtitle">
                This is your project name.
              </label>
              {!projectLoading ? (
                <input
                  type="text"
                  placeholder="e.g. argoapp-live"
                  className="settings-project-item-input"
                  value={repoName}
                  onChange={(e) => setRepoName(e.target.value)}
                />
              ) : (
                <Skeleton width={326} height={36} duration={2} />
              )}
            </div> */}
            <div className="settings-project-item">
              <label className="settings-project-item-title">Project Owner</label>
              <label className="settings-project-item-subtitle">
                This is the organization from which this project is deployed.
              </label>
              {!projectLoading ? (
                <div className="settings-project-value">
                  {selectedOrg?.profile.name}
                </div>
              ) : (
                <Skeleton width={326} height={36} duration={2} />
              )}
            </div>
            <div className="settings-project-item">
              <label className="settings-project-item-title">
                GitHub Repo/Branch
              </label>
              <label className="settings-project-item-subtitle">
                This is the organization from which this project is deployed.
              </label>
              {!projectLoading ? (
                <div className="settings-project-value">
                  {displayGithubRepo} (branch:{" "}
                  {selectedProject?.latestDeployment?.configuration.branch})
                </div>
              ) : (
                <Skeleton width={326} height={36} duration={2} />
              )}
            </div>
            <div className="settings-project-item">
              <label className="settings-project-item-title">
                Workspace directory
              </label>
              <label className="settings-project-item-subtitle">
                This is the project's client side workspace if you have a monorepo
                like structure.
              </label>
              {!projectLoading ? (
                <div className="settings-project-value">
                  {workspace ? workspace : "No Workspace"}
                </div>
              ) : (
                <Skeleton width={326} height={36} duration={2} />
              )}
            </div>
            <div className="settings-project-item">
              <label className="settings-project-item-title">Creation Date</label>
              <label className="settings-project-item-subtitle">
                This is the creation date of this project.
              </label>
              {!projectLoading ? (
                <div className="settings-project-value">{lastCreatedDate}</div>
              ) : (
                <Skeleton width={326} height={36} duration={2} />
              )}
            </div>
            <div className="settings-project-item">
              <label className="settings-project-item-title">Last Published</label>
              <label className="settings-project-item-subtitle">
                This is the creation date of this project.
              </label>
              {!projectLoading ? (
                <div className="settings-project-value">{lastPublishedDate}</div>
              ) : (
                <Skeleton width={326} height={36} duration={2} />
              )}
            </div>
          </div>
        </div>
        <div className="settings-project-details">
          <div className="settings-project-header delete-containers">
            {!projectLoading
              ? selectedProject?.state === "ARCHIVED"
                ? "Unarchive Project"
                : "Archive Project"
              : null}
          </div>
          <div className="settings-project-body">
            <div className="delete-org-container">
              {!projectLoading ? (
                <>
                  This project will be {archiveState}d and will{" "}
                  {archiveState === "archive" ? "not" : ""} be shown in your
                  organization's main directory.
                </>
              ) : (
                <Skeleton width={500} duration={2} />
              )}
              <br />
              <br />
              {!projectLoading ? (
                <b>
                  {archiveState === "archive"
                    ? "Note - You can view your archived projects in your organization settings."
                    : "Note - You can archive your project by clicking on the archive button on the project settings page."}
                </b>
              ) : (
                <Skeleton width={500} duration={2} />
              )}
            </div>
            <div className="settings-deployment-item">
              <>
                <div className="settings-deployment-left">
                  {!projectLoading ? (
                    <img
                      className="deployment-screenshot"
                      src={imageUrl(
                        selectedProject?.latestDeployment?.screenshot?.url,
                      )}
                      alt={"Preview not Available"}
                    />
                  ) : (
                    <Skeleton height={100} width={190} duration={2} />
                  )}
                  <div className="deployment-left-detail">
                    <div className="deployment-publish-detail">
                      <div className="deployment-header-title">
                        {!projectLoading ? (
                          selectedProject?.name
                        ) : (
                          <Skeleton width={300} duration={2} />
                        )}
                      </div>

                      <div className="deployment-header-description">
                        {!projectLoading ? (
                          <>Last updated at {lastPublishedDate}</>
                        ) : (
                          <Skeleton width={300} duration={2} />
                        )}
                      </div>
                    </div>
                    {/* <div className="deployment-commit-details">
                        <span className="bold-text">Production: </span>
                        <span>
                          {deployment?.configuration.branch}
                          {deployment?.commitId ? (
                            <>
                              @
                              <a
                                href={`${selectedProject?.githubUrl.substring(
                                  0,
                                  selectedProject?.githubUrl.length - 4,
                                )}/commit/${deployment?.commitId}`}
                                target="_blank"
                                rel="noopener noreferrer"
                                className="deployment-link"
                              >
                                {deployment?.commitId.substr(0, 7)}{" "}
                                {deployment?.commitMessage
                                  ? `- ${deployment?.commitMessage.substr(0, 64)}...`
                                  : ""}
                              </a>
                            </>
                          ) : null}
                        </span>
                      </div> */}
                    {/* <div className="protocol-tag-container">
                      {showProtocolTag(deployment?.configuration.protocol!)}
                    </div> */}
                  </div>
                  {/* <div className="deployment-time-details">
                    <div className="bold-text">
                      {moment(`${deployment?.createdAt}`).format("MMM DD")} at{" "}
                      {moment(`${deployment?.createdAt}`).format("hh:mm A")}
                    </div>
                    <div className="deployment-status">
                      <span className="deployment-status-icon">
                        {deployment?.status.toLowerCase() === "pending" && (
                          <Lottie options={defaultOptions} height={42} width={58} />
                        )}
                        {deployment?.status.toLowerCase() === "deployed" && (
                          <LazyLoadedImage height={16} once>
                            <img
                              src={require("../../../../../../assets/svg/rocket_background.svg")}
                              alt="rocket"
                              className="rocket-icon"
                              height={16}
                              width={16}
                              loading="lazy"
                            />
                          </LazyLoadedImage>
                        )}
                        {deployment?.status.toLowerCase() === "failed" && (
                          <LazyLoadedImage height={16} once>
                            <img
                              src={require("../../../../../../assets/svg/error.svg")}
                              alt="rocket"
                              className="rocket-icon"
                              height={16}
                              width={16}
                              loading="lazy"
                            />
                          </LazyLoadedImage>
                        )}
                      </span>
                      {deployment?.status}
                    </div>
                  </div> */}
                </div>
              </>
              {/* {type === "skeleton" && (
                <>
                  <div className="deployment-left">
                    <Skeleton height={100} width={190} duration={2} />
                    <div className="deployment-left-detail">
                      <div className="deployment-publish-detail">
                        <Skeleton width={300} duration={2} />
                      </div>
                      <div className="deployment-commit-details">
                        <Skeleton width={180} duration={2} />
                      </div>
                    </div>
                    <div className="deployment-time-details">
                      <div className="bold-text">
                        <Skeleton width={60} duration={2} />
                      </div>
                    </div>
                  </div>
                </>
              )} */}
            </div>
            {!projectLoading ? (
              <div className="delete-org-confirm-container">
                <span className="confirm-checkbox">
                  <input
                    type="checkbox"
                    checked={archiveConfirmed}
                    onChange={(e) => setArchiveConfirmed(!archiveConfirmed)}
                  />
                </span>
                <span>
                  <b>I confirm that I want to {archiveState} this project</b>
                </span>
              </div>
            ) : (
              <></>
            )}
            {!projectLoading ? (
              <div className="settings-project-footer delete-containers">
                <div className="warning-text-container">
                  <span className="exclamation-icon">
                    <FontAwesomeIcon icon={faExclamationCircle}></FontAwesomeIcon>
                  </span>
                  <span>
                    Please confirm and click {archiveState} to {archiveState} this
                    project
                  </span>
                </div>
                {selectedProject?.state === "ARCHIVED" ? (
                  <button
                    type="button"
                    className="primary-button archive-button"
                    disabled={!archiveConfirmed}
                    onClick={projectMaintain}
                  >
                    <div className="bounceLoader">
                      {archiveLoading && (
                        <BounceLoader size={20} color={"#fff"} loading={true} />
                      )}
                    </div>
                    Unarchive
                  </button>
                ) : (
                  <button
                    type="button"
                    className="primary-button archive-button"
                    disabled={!archiveConfirmed}
                    onClick={projectArchive}
                  >
                    {archiveLoading && (
                      <div className="bounceLoader">
                        <BounceLoader size={20} color={"#fff"} loading={true} />
                      </div>
                    )}
                    Archive
                  </button>
                )}
              </div>
            ) : null}
            {/* <div className="archive-button-container">
              <button
                type="button"
                className="primary-button archive-button"
                // disabled={!deleteConfirmed}
                // onClick={deleteOrg}
              >
                Archive
              </button>
            </div> */}
          </div>
        </div>
      </div>
    </div>
  );
}
Example #11
Source File: GenerateResolverSkylink.tsx    From argo-react with MIT License 4 votes vote down vote up
GenerateResolverSkylink: React.FC<IGenerateResolverSkylinkProps> = ({
  type,
  resolver,
  close,
}) => {
  const { projectLoading, selectedProject, selectedOrg } =
    useContext<IStateModel>(StateContext);
  const { fetchProject } = useContext<IActionModel>(ActionContext);

  const skylinksList = !projectLoading
    ? selectedProject
      ? selectedProject?.deployments
          .filter((deployment) => deployment?.status?.toLowerCase() === "deployed")
          .filter(
            (deployment) => deployment?.sitePreview?.indexOf("siasky.net") !== -1,
          )
          .sort((a, b) => moment(b?.createdAt).diff(moment(a?.createdAt)))
      : []
    : [];

  const [mySky, setMySky] = useState<MySky>();
  const [skynetSeed, setSkynetSeed] = useState<string>("");
  const [name, setName] = useState<string>("");
  const [resolverSkylink, setResolverSkylink] = useState<string>("");
  const [step, setStep] = useState<number>(1);
  const [userID, setUserID] = useState<string>("");
  const [useSeed, setUseSeed] = useState<boolean>(false);
  const [popupLoading, setPopupLoading] = useState<boolean>(true);
  const [skylinkLoading, setSkylinkLoading] = useState<boolean>(false);
  const [errorWarning, setErrorWarning] = useState<boolean>(false);
  const [errorMessage, setErrorMessage] = useState<string>("");
  const [latestSkylink, setLatestSkylink] = useState<string>("");
  const componentIsMounted = useRef<boolean>(true);

  useEffect(() => {
    async function initMySky() {
      setPopupLoading(true);
      try {
        const mySky = await client.loadMySky(dataDomain);
        const loggedIn = await mySky.checkLogin();
        if (componentIsMounted.current) {
          setMySky(mySky);
          if (loggedIn) {
            setUseSeed(false);
            setUserID(await mySky.userID());
            setStep(2);
            setPopupLoading(false);
          } else {
            setPopupLoading(false);
          }
        }
      } catch (e) {
        console.error(e);
        setPopupLoading(false);
      }
    }

    initMySky();

    return () => {
      handleMySkyLogout();
      componentIsMounted.current = false;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    if (resolver && type !== "create") {
      setName(resolver.name);
      setLatestSkylink(`https://siasky.net/${resolver.targetSkylink}/`);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [resolver]);

  const loginMySky = async () => {
    if (mySky) {
      await mySky.addPermissions(
        new Permission(
          window.location.hostname,
          dataDomain,
          PermCategory.Discoverable,
          PermType.Write,
        ),
      );
      const status = await mySky.requestLoginAccess();
      if (componentIsMounted.current) {
        if (status) {
          setUserID(await mySky.userID());
          setStep(2);
        }
      }
    }
  };

  const handleMySkyLogout = async () => {
    if (componentIsMounted.current) {
      setUserID("");
      setStep(1);
      setUseSeed(false);
    }
  };

  const generateResolverSkylink = async () => {
    try {
      setSkylinkLoading(true);
      if (!useSeed) {
        if (mySky) {
          await mySky.setDataLink(
            `${dataDomain}/${selectedProject?._id}/${name}`,
            latestSkylink.split("https://siasky.net/")[1].slice(0, -1),
          );
          const resolverSkylink = await mySky.getEntryLink(
            `${dataDomain}/${selectedProject?._id}/${name}`,
          );
          if (type === "create") {
            addResolverSkylinks(resolverSkylink);
          } else {
            editResolverSkylinks(resolverSkylink);
          }
        }
      } else {
        const { publicKey, privateKey } = genKeyPairFromSeed(skynetSeed);
        await client.db.setDataLink(
          privateKey,
          `${dataDomain}/${selectedProject?._id}/${name}`,
          latestSkylink.split("https://siasky.net/")[1].slice(0, -1),
        );
        const resolverSkylink = await client.registry.getEntryLink(
          publicKey,
          `${dataDomain}/${selectedProject?._id}/${name}`,
        );
        if (type === "create") {
          addResolverSkylinks(resolverSkylink);
        } else {
          editResolverSkylinks(resolverSkylink);
        }
      }
    } catch (err) {
      // eslint-disable-next-line no-console
      console.log((err as Error).message);
      setErrorWarning(true);
      setErrorMessage((err as Error).message);
      setTimeout(() => {
        setErrorWarning(false);
        setErrorMessage("");
      }, 5000);
      setSkylinkLoading(false);
    }
  };

  const addResolverSkylinks = (resolverSkylink: string) => {
    const details = {
      orgId: selectedOrg?._id,
      projectId: selectedProject!._id,
      name,
      resolverSkylink: resolverSkylink.split("sia://")[1],
      targetSkylink: latestSkylink.split("https://siasky.net/")[1].slice(0, -1),
    };

    ApiService.addResolverSkylinks(details).subscribe(
      (result) => {
        if (result.success) {
          setName("");
          setResolverSkylink(resolverSkylink);
          setSkylinkLoading(false);
          setStep(3);
          fetchProject(`${selectedProject?._id}`);
        } else {
          setName("");
          setSkylinkLoading(false);
          setErrorWarning(true);
          setErrorMessage(result.message);
          setTimeout(() => {
            setErrorWarning(false);
            setErrorMessage("");
          }, 5000);
        }
      },
      (err) => {
        setErrorWarning(true);
        setErrorMessage(err.message);
        setTimeout(() => {
          setErrorWarning(false);
          setErrorMessage("");
        }, 5000);
      },
    );
  };

  const editResolverSkylinks = (resolverSkylink: string) => {
    const details = {
      orgId: selectedOrg?._id,
      projectId: selectedProject!._id,
      resolverSkylink: resolverSkylink.split("sia://")[1],
      targetSkylink: latestSkylink.split("https://siasky.net/")[1].slice(0, -1),
    };

    ApiService.editResolverSkylinks(resolver?._id || "", details).subscribe(
      (result) => {
        if (result.success) {
          setName("");
          setResolverSkylink(resolverSkylink);
          setSkylinkLoading(false);
          setStep(3);
          fetchProject(`${selectedProject?._id}`);
        } else {
          setSkylinkLoading(false);
          setErrorWarning(true);
          setErrorMessage(result.message);
          setTimeout(() => {
            setErrorWarning(false);
            setErrorMessage("");
          }, 5000);
        }
      },
      (err) => {
        setErrorWarning(true);
        setErrorMessage(err.message);
        setTimeout(() => {
          setErrorWarning(false);
          setErrorMessage("");
        }, 5000);
      },
    );
  };

  const deleteResolverSkylink = async () => {
    try {
      setSkylinkLoading(true);
      if (!useSeed) {
        if (mySky) {
          await mySky.setEntryData(
            `${dataDomain}/${selectedProject?._id}/${name}`,
            new Uint8Array(RAW_SKYLINK_SIZE),
          );
          removeResolverSkylink(resolver?._id || "");
        }
      } else {
        const { publicKey, privateKey } = genKeyPairFromSeed(skynetSeed);
        const reg = await client.registry.getEntry(
          publicKey,
          `${dataDomain}/${selectedProject?._id}/${name}`,
        );
        const revision = reg.entry ? reg.entry.revision + BigInt(1) : BigInt(1);
        await client.registry.setEntry(privateKey, {
          dataKey: `${dataDomain}/${selectedProject?._id}/${name}`,
          data: new Uint8Array(RAW_SKYLINK_SIZE),
          revision,
        });
        removeResolverSkylink(resolver?._id || "");
      }
    } catch (err) {
      // eslint-disable-next-line no-console
      console.log((err as Error).message);
      setErrorWarning(true);
      setErrorMessage((err as Error).message);
      setTimeout(() => {
        setErrorWarning(false);
        setErrorMessage("");
      }, 5000);
      setSkylinkLoading(false);
    }
  };

  const removeResolverSkylink = async (id: string) => {
    ApiService.deleteResolverSkylinks(id, {
      orgId: selectedOrg?._id,
      projectId: selectedProject?._id,
    }).subscribe(
      (result) => {
        if (result.success) {
          setName("");
          setResolverSkylink(resolverSkylink);
          setSkylinkLoading(false);
          setStep(3);
          fetchProject(`${selectedProject?._id}`);
        } else {
          setSkylinkLoading(false);
          setErrorWarning(true);
          setErrorMessage(result.message);
          setTimeout(() => {
            setErrorWarning(false);
            setErrorMessage("");
          }, 5000);
        }
      },
      (err) => {
        setSkylinkLoading(false);
        setErrorWarning(true);
        setErrorMessage(err.message);
        setTimeout(() => {
          setErrorWarning(false);
          setErrorMessage("");
        }, 5000);
      },
    );
  };

  const logoutMySky = async () => {
    const mySky = await client.loadMySky(dataDomain);
    const loggedIn = await mySky.checkLogin();
    if (loggedIn) {
      await mySky.logout();
      setStep(1);
    }
  };

  const defaultOptions = {
    loop: true,
    autoplay: true,
    animationData,
    rendererSettings: {
      preserveAspectRatio: "xMidYMid",
    },
  };

  return (
    <div className="GenerateResolverSkylink">
      <div className="close-button" onClick={(e) => close()}>
        <FontAwesomeIcon icon={faTimes}></FontAwesomeIcon>
      </div>
      <div className="modal-container">
        {!popupLoading ? (
          <div className="modal-body">
            {type === "create" && (
              <h3 className="modal-title">Generate Resolver Skylink</h3>
            )}
            {type === "update" && (
              <h3 className="modal-title">Update Resolver Skylink</h3>
            )}
            {type === "remove" && (
              <h3 className="modal-title">Remove Resolver Skylink</h3>
            )}
            {step !== 3 ? (
              <p className="modal-content">
                Resolver skylinks are a special type of skylink that enables skylinks
                whose underlying data changes. When resolver skylinks are accessed on
                Skynet, they "resolve" to other skylinks whose data is returned to
                the requester.
              </p>
            ) : null}
            {step === 1 && (
              <div className="connect-container">
                <div className="seed-phrase-container">
                  <input
                    type="text"
                    placeholder="Enter your seed phrase..."
                    className="text-input"
                    value={skynetSeed}
                    onChange={(e) => setSkynetSeed(e.target.value)}
                  />
                </div>
                <button
                  className="connect-mysky-button"
                  onClick={(e) => {
                    setUseSeed(true);
                    setStep(2);
                  }}
                  type="button"
                >
                  Login with Seed
                </button>
                <div className="or-text">Or</div>
                <div>
                  <button
                    className="connect-mysky-button"
                    onClick={loginMySky}
                    type="button"
                  >
                    Login with MySky
                  </button>
                </div>
              </div>
            )}
            {step === 2 && (
              <div className="connect-container">
                {!useSeed && (
                  <div className="user-container">
                    <div className="skylink-name-container">
                      <label>User ID</label>
                      <div className="skylink-name small-text">
                        {userID.substring(0, 51)}
                      </div>
                    </div>
                    <div className="margin-left">
                      <button
                        className="logout-mysky-button"
                        onClick={logoutMySky}
                        type="button"
                      >
                        Logout
                      </button>
                    </div>
                  </div>
                )}
                <div className="skylink-name-container">
                  <label>Name</label>
                  <div className="skylink-name">
                    {type === "create" && (
                      <input
                        type="text"
                        placeholder="eg: 'argoapp.hns' or 'my custom name'"
                        className="text-input"
                        value={name}
                        onChange={(e) => setName(e.target.value)}
                      />
                    )}
                    {(type === "update" || type === "remove") && name}
                  </div>
                </div>
                <div className="skylink-name-container">
                  <label>Skylink</label>
                  <div className="skylink-select-container">
                    <select
                      className="skylink-select"
                      value={latestSkylink}
                      disabled={type === "remove"}
                      onChange={(e) => setLatestSkylink(e.target.value)}
                    >
                      <option value="">Select Skylinks</option>
                      {(skylinksList ? skylinksList : []).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>
                  {type === "create" && (
                    <button
                      className="connect-mysky-button"
                      onClick={generateResolverSkylink}
                      disabled={!name || !latestSkylink}
                      type="button"
                    >
                      {skylinkLoading && (
                        <span className="space-between">
                          <BounceLoader size={20} color={"#fff"} loading={true} />
                        </span>
                      )}
                      Generate
                    </button>
                  )}
                  {type === "update" && (
                    <button
                      className="connect-mysky-button"
                      onClick={generateResolverSkylink}
                      disabled={!name || !latestSkylink}
                      type="button"
                    >
                      {skylinkLoading && (
                        <span className="space-between">
                          <BounceLoader size={20} color={"#fff"} loading={true} />
                        </span>
                      )}
                      Update
                    </button>
                  )}
                  {type === "remove" && (
                    <button
                      className="connect-mysky-button"
                      onClick={deleteResolverSkylink}
                      disabled={!name || !latestSkylink}
                      type="button"
                    >
                      {skylinkLoading && (
                        <span className="space-between">
                          <BounceLoader size={20} color={"#fff"} loading={true} />
                        </span>
                      )}
                      Remove
                    </button>
                  )}
                </div>
              </div>
            )}
            {step === 3 && (
              <div className="success-container">
                <div className="check-container">
                  <Lottie options={defaultOptions} height={170} />
                </div>
                <div className="header-container">Success!</div>
                <div className="text-description">
                  {type === "create" &&
                    "Resolver Skylink has been generated successfully."}
                  {type === "update" &&
                    "Resolver Skylink has been updated successfully."}
                  {type === "remove" &&
                    "Resolver Skylink has been removed successfully."}
                </div>
                <a
                  className="resolver-link"
                  href={`http://siasky.net/${resolverSkylink.split("sia://")[1]}`}
                  target="_blank"
                  rel="noopener noreferrer"
                >
                  {resolverSkylink}
                </a>
              </div>
            )}
            {errorWarning && (
              <div className="warning-container">
                <div className="warning-header">
                  <FontAwesomeIcon icon={faExclamationCircle} /> {errorMessage}
                </div>
              </div>
            )}
          </div>
        ) : (
          <div className="loading-container">
            <GridLoader size={32} color={"#3664ae"} loading={true} />
          </div>
        )}
      </div>
    </div>
  );
}
Example #12
Source File: InviteCallback.tsx    From argo-react with MIT License 4 votes vote down vote up
function InviteCallback() {
  const location = useLocation();
  const history = useHistory();

  const { fetchUser } = useContext(ActionContext);
  const [inviteStatus, setInviteStatus] = useState<string>("");
  const [errorWarning, setErrorWarning] = useState<boolean>(false);
  const [inviteLoading, setInviteLoading] = useState<boolean>(false);

  useEffect(() => {
    const jwtToken = localStorage.getItem("jwt-token");
    if (!jwtToken) {
      const query = new URLSearchParams(location.search);
      const ref = query.get("ref");
      const orgName = query.get("orgName");
      localStorage.setItem("inviteRef", `${ref}`);
      localStorage.setItem("orgName", `${orgName}`);
      history.push("/signup");
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const componentIsMounted = useRef(true);

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

  const sendInvitation = (pValue: string) => {
    if (pValue === "accepts") {
      setInviteLoading(true);
    }
    const query = new URLSearchParams(location.search);
    const ref = query.get("ref") || localStorage.getItem("inviteRef");
    const inviteReply = {
      id: ref,
      status: pValue,
    };
    ApiService.updateInvite(inviteReply).subscribe(
      (res) => {
        if (componentIsMounted.current) {
          if (!res.error) {
            setInviteStatus(res.message);
            setErrorWarning(false);
            localStorage.removeItem("inviteRef");
            fetchUser();
            history.push("/dashboard");
          } else {
            setInviteStatus(res.message);
            setErrorWarning(true);
          }
        }
        setInviteLoading(false);
        setTimeout(() => {
          setErrorWarning(false);
          setInviteStatus("");
        }, 5000);
      },
      (err) => {
        setErrorWarning(true);
        setInviteStatus(err.message);
      },
    );
  };

  return (
    <div className="InviteCallback">
      <RootHeader parent={"InviteCallback"} />
      <main className="app-main">
        <div className="invite-callback-container">
          <div className="invite-callback-card">
            <div className="invite-callback-card-inner">
              <h1 className="invite-callback-title">Accept the invitation</h1>
              <div className="create-org-form">
                <label className="create-org-form-title">
                  Invitation from {localStorage.getItem("orgName")} Organization
                </label>
                <label className="create-org-form-subtitle">
                  You have been invited to join the {localStorage.getItem("orgName")}{" "}
                  organization on ArGo.
                </label>
                <label className="create-org-form-subtitle">
                  By joining, your name, email address and username will be visible
                  to other members of the organization. You will also be able to
                  access all the projects on the Organization.
                </label>
              </div>
              <div className="button-container">
                <button
                  type="button"
                  className="accept-button"
                  onClick={(e) => sendInvitation("accepts")}
                >
                  {inviteLoading && (
                    <BounceLoader size={20} color={"#fff"} loading={true} />
                  )}
                  Accept
                </button>
                <button
                  type="button"
                  className="decline-button"
                  onClick={(e) => sendInvitation("decline")}
                >
                  Decline
                </button>
              </div>
              {errorWarning && (
                <div className="warning-container">
                  <div className="warning-header">
                    <FontAwesomeIcon icon={faExclamationCircle} /> {inviteStatus}
                  </div>
                </div>
              )}
            </div>
          </div>
        </div>
      </main>
    </div>
  );
}
Example #13
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 #14
Source File: SettingsGeneral.tsx    From argo-react with MIT License 4 votes vote down vote up
SettingsGeneral = () => {
  // const history = useHistory();
  const { selectedOrg, orgLoading } = useContext(StateContext);
  const { fetchUser } = useContext(ActionContext);

  const [orgUsername, setOrgUsername] = useState<string>("");
  const [orgName, setOrgName] = useState<string>("");
  const [orgAvatar, setOrgAvatar] = useState<string>("");
  const [isDataChanged, setIsDataChanged] = useState<boolean>(false);
  // const [deleteConfirmed, setDeleteConfirmed] = useState<boolean>(false);
  const [updateLoading, setUpdateLoading] = useState<boolean>(false);
  // const [deleteLoading, setDeleteLoading] = useState<boolean>(false);

  useEffect(() => {
    if (selectedOrg) {
      setOrgUsername(selectedOrg.profile.username);
      setOrgName(selectedOrg.profile.name);
      setOrgAvatar(selectedOrg.profile.image);
    }
  }, [selectedOrg]);

  useEffect(() => {
    if (selectedOrg) {
      if (
        selectedOrg.profile.username !== orgUsername ||
        selectedOrg.profile.name !== orgName ||
        selectedOrg.profile.image !== orgAvatar
      ) {
        setIsDataChanged(true);
      } else {
        setIsDataChanged(false);
      }
    }
  }, [selectedOrg, orgUsername, orgName, orgAvatar]);

  const fileUpload = (file: Blob) => {
    const reader: FileReader = new window.FileReader();
    reader.readAsDataURL(file);
    reader.onloadend = () => saveURL(reader);
  };

  const saveURL = async (reader: FileReader) => {
    setOrgAvatar(`${reader.result}`);
  };

  const updateOrganization = () => {
    if (selectedOrg) {
      setUpdateLoading(true);
      const org = {
        username: orgUsername,
        name: orgName,
        image: orgAvatar,
      };

      ApiService.updateOrganization(`${selectedOrg?._id}`, org).subscribe(
        (result) => {
          setUpdateLoading(false);
          fetchUser();
        },
      );
    }
  };

  // const deleteOrg = () => {
  //   if (selectedOrg && deleteConfirmed) {
  //     setDeleteLoading(true);
  //     ApiService.deleteOrganization((selectedOrg as any)._id).subscribe((result) => {
  //       setDeleteLoading(false);
  //       fetchUser();
  //       history.push("/dashboard");
  //     });
  //   }
  // };
  return (
    <div className="OrgSettingsGeneral">
      <div className="settings-right-container">
        <div className="settings-profile-details">
          <div className="settings-profile-header">Organisation Details</div>
          <div className="settings-profile-body">
            <div className="settings-profile-item">
              <label className="settings-profile-item-title">
                Organisation Username
              </label>
              <label className="settings-profile-item-subtitle">
                This is your organization username.
              </label>
              {!orgLoading ? (
                <input
                  type="text"
                  placeholder="e.g. argoapp-live"
                  className="settings-profile-item-input"
                  value={orgUsername}
                  onChange={(e) => setOrgUsername(e.target.value)}
                />
              ) : (
                <Skeleton width={326} height={36} duration={2} />
              )}
            </div>
            <div className="settings-profile-item">
              <label className="settings-profile-item-title">
                Organisation Name
              </label>
              <label className="settings-profile-item-subtitle">
                Please enter your team's name, or a display name you are comfortable
                with.
              </label>
              {!orgLoading ? (
                <input
                  type="text"
                  placeholder="e.g. ArGo Team"
                  className="settings-profile-item-input"
                  value={orgName}
                  onChange={(e) => setOrgName(e.target.value)}
                />
              ) : (
                <Skeleton width={326} height={36} duration={2} />
              )}
            </div>
            {/* <div className="settings-profile-item">
                <label className="settings-profile-item-title">Your Email</label>
                <label className="settings-profile-item-subtitle">
                  This is your email address connected with the OAuth provider. We
                  don't allow it to be edited as of now.
                </label>
                <input
                  type="text"
                  placeholder="e.g. jthely"
                  className="settings-profile-item-input"
                />
              </div> */}
            <div className="settings-profile-item avatar-container">
              <div className="settings-profile-item-avatar-container">
                <label className="settings-profile-item-title">
                  Organisation Avatar
                </label>
                <label className="settings-profile-item-subtitle">
                  This is your organisation's avatar.
                </label>
                <label className="settings-profile-item-subtitle">
                  Click on the avatar to upload a custom one from your files.
                </label>
              </div>
              <div className="settings-profile-avatar-image-container">
                {!orgLoading ? (
                  <>
                    <input
                      type="file"
                      className="file-upload"
                      onChange={(e) =>
                        e.target.files ? fileUpload(e.target.files[0]) : undefined
                      }
                    />
                    <LazyLoadedImage height={64} once>
                      <img
                        src={
                          orgAvatar
                            ? orgAvatar
                            : require("../../../../../../assets/svg/camera_grad.svg")
                        }
                        alt="avatar"
                        className="settings-avatar"
                        height={64}
                        width={64}
                        loading="lazy"
                      />
                    </LazyLoadedImage>
                  </>
                ) : (
                  <Skeleton circle={true} height={64} width={64} duration={2} />
                )}
              </div>
            </div>
          </div>
          <div className="settings-profile-footer">
            <div className="warning-text-container">
              <span className="exclamation-icon">
                <FontAwesomeIcon icon={faExclamationCircle}></FontAwesomeIcon>
              </span>
              <span>Click save to update your organisation</span>
            </div>
            <button
              type="button"
              className="primary-button"
              disabled={orgLoading || !isDataChanged}
              onClick={updateOrganization}
            >
              {updateLoading && (
                <BounceLoader size={20} color={"#fff"} loading={true} />
              )}
              Save
            </button>
          </div>
        </div>
        {/* <div className="settings-profile-details">
          <div className="settings-profile-header delete-containers">
            Delete Organisation
          </div>
          <div className="settings-profile-body">
            <div className="delete-org-container">
              This action will queue the removal of all your team's data, including:
              <br /> Deployments, Member associations, Activity, Aliases, Domains,
              Certificates and your Billing subscription.
            </div>
            <div className="delete-org-confirm-container">
              <span className="confirm-checkbox">
                <input
                  type="checkbox"
                  checked={deleteConfirmed}
                  onChange={(e) => setDeleteConfirmed(!deleteConfirmed)}
                />
              </span>
              <span>
                Confirm that I want to irreversibly delete the team{" "}
                {selectedOrg?.profile.name}
              </span>
            </div>
          </div>
          <div className="settings-profile-footer delete-containers">
            <div className="warning-text-container">
              <span className="exclamation-icon">
                <FontAwesomeIcon icon={faExclamationCircle}></FontAwesomeIcon>
              </span>
              <span>
                Please confirm and click delete to delete your organisation
              </span>
            </div>
            <button
              type="button"
              className="delete-button"
              disabled={!deleteConfirmed}
              onClick={deleteOrg}
            >
              {deleteLoading && (
                <BounceLoader size={20} color={"#fff"} loading={true} />
              )}
              Delete
            </button>
          </div>
        </div> */}
      </div>
    </div>
  );
}
Example #15
Source File: Overview.tsx    From argo-react with MIT License 4 votes vote down vote up
Overview = () => {
  const timeAgo = new TimeAgo("en-US");

  const history = useHistory();

  const { selectedOrg, orgLoading } = useContext<IStateModel>(StateContext);
  const { setRepoForTriggerDeployment } = useContext<IActionModel>(ActionContext);

  return (
    <div className="Overview">
      {!selectedOrg?.wallet && !orgLoading ? (
        <div className="overview-alert">
          <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>
      ) : null}
      <div className="overview-container">
        <div className="overview-team-avatar-container">
          {!orgLoading ? (
            <LazyLoadedImage height={120} once>
              <img
                src={
                  selectedOrg?.profile?.image
                    ? selectedOrg.profile.image
                    : require("../../../../assets/png/default_icon.png")
                }
                alt="org"
                className="team-avatar"
                height={120}
                width={120}
                loading="lazy"
              />
            </LazyLoadedImage>
          ) : (
            <Skeleton circle={true} height={120} width={120} duration={2} />
          )}
        </div>
        <div className="overview-team-details-container">
          <h1 className="overview-team-name">
            {!orgLoading ? (
              selectedOrg?.profile?.name
            ) : (
              <Skeleton width={150} duration={2} />
            )}
          </h1>
          {!orgLoading ? (
            <div className="overview-team-misc-container">
              <div className="overview-team-detail-container">
                <label className="overview-team-detail-label">Members</label>
                <div
                  className="overview-team-member-value"
                  onClick={(e) => history.push("/dashboard/members")}
                >
                  {selectedOrg?.users?.length}
                </div>
              </div>
              <div className="overview-team-detail-container git-integration-container">
                <label className="overview-team-detail-label">Projects</label>
                <div className="overview-team-member-value">
                  {selectedOrg?.projects?.length}
                </div>
              </div>
              {/* <div className="overview-team-detail-container git-integration-container">
              <label className="overview-team-detail-label">Git Integration</label>
              <div className="overview-team-git-integration-value">
                <FontAwesomeIcon icon={faGithub}></FontAwesomeIcon>
                <span className="git-name">argoapp-live</span>
              </div>
            </div> */}
            </div>
          ) : (
            <div className="overview-team-misc-container">
              <Skeleton width={250} duration={2} />
            </div>
          )}
        </div>
        <div className="buttons-container">
          <button
            type="button"
            className="secondary-button"
            disabled={orgLoading}
            onClick={(e) => history.push("/dashboard/members/new")}
          >
            Invite Members
          </button>
          <button
            type="button"
            className="primary-button"
            disabled={orgLoading}
            onClick={(e) => {
              setRepoForTriggerDeployment(null);
              history.push("/deploy/new");
            }}
          >
            Deploy
          </button>
        </div>
      </div>
      <div className="project-list-container">
        <ul className="project-list">
          {!orgLoading ? (
            selectedOrg?.projects?.length ? (
              selectedOrg?.projects?.map((repo: IProject, index: number) => (
                <div key={index}>
                  <ProjectItem
                    type="filled"
                    projectName={repo?.name}
                    domains={repo.domains?.length ? repo.domains : []}
                    subdomains={repo.subdomains?.length ? repo.subdomains : []}
                    hnsDomains={
                      repo.handshakeDomains?.length ? repo.handshakeDomains : []
                    }
                    hnsSubdomains={
                      repo.handshakeSubdomains?.length
                        ? repo.handshakeSubdomains
                        : []
                    }
                    ensDomains={repo.ensDomains?.length ? repo.ensDomains : []}
                    latestDeployment={
                      repo?.latestDeployment?.sitePreview
                        ? repo?.latestDeployment?.sitePreview
                        : ""
                    }
                    githubUrl={repo?.githubUrl}
                    updateTime={timeAgo.format(new Date(`${repo?.updatedAt}`))}
                    repo={repo}
                    index={index}
                  />
                </div>
              ))
            ) : (
              <ProjectItem
                type="empty"
                projectName={null}
                domains={null}
                subdomains={null}
                hnsDomains={null}
                hnsSubdomains={null}
                ensDomains={null}
                latestDeployment={null}
                githubUrl={null}
                updateTime={null}
                repo={null}
                index={1}
              />
            )
          ) : (
            <ProjectItem
              type="skeleton"
              projectName={null}
              domains={null}
              subdomains={null}
              hnsDomains={null}
              hnsSubdomains={null}
              ensDomains={null}
              latestDeployment={null}
              githubUrl={null}
              updateTime={null}
              repo={null}
              index={1}
            />
          )}
        </ul>
      </div>
    </div>
  );
}
Example #16
Source File: InviteMembers.tsx    From argo-react with MIT License 4 votes vote down vote up
InviteMembers = () => {
  const history = useHistory();
  const { selectedOrg, user } = useContext(StateContext);
  const [inviteMembers, setInviteMembers] = useState<string>("");
  const [inviteMemberLoading, setInviteMembersLoading] = useState<boolean>(false);
  const [inviteData, setInviteData] = useState<boolean>();
  const [validateEmail, setValidateEmail] = useState<boolean>(false);
  const [popupIsOpen, setPopupIsOpen] = useState<boolean>(false);

  useEffect(() => {
    if (
      inviteMembers.match("^([a-zA-Z0-9+_.-]+@[a-zA-Z0-9.-]+,?)*$") &&
      inviteMembers !== ""
    ) {
      setValidateEmail(true);
    } else {
      setValidateEmail(false);
    }
  }, [inviteMembers]);

  const sendInvite = () => {
    setInviteMembersLoading(true);
    const members = inviteMembers.split(",").map((member) => member.trim());
    const invites = members.map((member) => ({
      organization: selectedOrg?._id,
      orgName: selectedOrg?.profile.name,
      userEmail: member,
      invitingUser: user?.argoProfile.name,
    }));
    concat(invites.map((invite) => ApiService.sendMemberInvite(invite))).subscribe(
      (res) =>
        res.subscribe((data) => {
          setInviteData(data.success);
          setInviteMembersLoading(false);
        }),
    );
  };

  return (
    <div className="InviteMembers">
      <div className="invite-members-container">
        <div className="invite-members-details">
          <div className="invite-members-inner">
            <h1 className="invite-members-title">Add team members</h1>
            <div className="invite-members-form">
              <label className="invite-members-form-title">
                Emails of the new members
              </label>
              <label className="invite-members-form-subtitle">
                New team members will get an email with a link to accept the
                invitation.
              </label>
              <div className="invite-input-container">
                <span className="mail-icon">
                  <FontAwesomeIcon icon={faEnvelope}></FontAwesomeIcon>
                </span>
                <input
                  type="text"
                  className="invite-members-form-input"
                  placeholder="[email protected]"
                  value={inviteMembers}
                  onChange={(e) => setInviteMembers(e.target.value)}
                />
              </div>
              {!validateEmail && (
                <label className="invite-members-form-alert">
                  <span className="alert-icon-container">
                    <FontAwesomeIcon icon={faExclamationCircle}></FontAwesomeIcon>
                  </span>
                  Please enter the email in a valid format.
                </label>
              )}
              <label className="invite-members-form-subtitle-bottom">
                You can enter several email addresses separated by commas <br />
                (without spaces).
              </label>
            </div>
            <div className="button-container">
              <div>
                <button
                  type="button"
                  className="primary-button"
                  onClick={() => {
                    sendInvite();
                    setPopupIsOpen(true);
                  }}
                  disabled={!validateEmail}
                >
                  {inviteMemberLoading && (
                    <BounceLoader size={20} color={"#fff"} loading={true} />
                  )}
                  Send
                </button>
              </div>
              <InvitePopup
                isOpen={popupIsOpen}
                memberLoading={inviteMemberLoading}
                isData={inviteData!}
              />
              <button
                type="button"
                className="cancel-button"
                onClick={(e) => history.goBack()}
              >
                Cancel
              </button>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}
Example #17
Source File: TxnsPage.tsx    From devex with GNU General Public License v3.0 4 votes vote down vote up
TxnsPage: React.FC = () => {

  const networkContext = useContext(NetworkContext)
  const { dataService } = networkContext!

  const fetchIdRef = useRef(0)
  const [isLoading, setIsLoading] = useState(false)
  const [pageCount, setPageCount] = useState(0)
  const [data, setData] = useState<TransactionDetails[] | null>(null)
  const [recentTxnHashes, setRecentTxnHashes] = useState<string[] | null>(null)

  const columns = useMemo(
    () => [{
      id: 'from-col',
      Header: 'From',
      accessor: 'txn.senderAddress',
      Cell: ({ value }: { value: string }) => (
        <QueryPreservingLink to={`/address/${hexAddrToZilAddr(value)}`}>
          {hexAddrToZilAddr(value)}
        </QueryPreservingLink>)
    }, {
      id: 'to-col',
      Header: 'To',
      Cell: ({ row }: { row: Row<TransactionDetails> }) => {
        return <ToAddrDisp txnDetails={row.original} />
      }
    }, {
      id: 'hash-col',
      Header: 'Hash',
      accessor: 'hash',
      Cell: ({ row }: { row: Row<TransactionDetails> }) => {
        console.log(row)
        return <QueryPreservingLink to={`/tx/0x${row.original.hash}`}>
          <div className='text-right mono'>
            {row.original.txn.txParams.receipt && !row.original.txn.txParams.receipt.success
              && <FontAwesomeIcon className='mr-1' icon={faExclamationCircle} color='red' />
            }
            {'0x' + row.original.hash}
          </div>
        </QueryPreservingLink>
      }
    }, {
      id: 'amount-col',
      Header: 'Amount',
      accessor: 'txn.amount',
      Cell: ({ value }: { value: string }) => (
        <OverlayTrigger placement='right'
          overlay={<Tooltip id={'amt-tt'}>{qaToZil(value)}</Tooltip>}>
          <div className='text-right sm'>{qaToZil(value, 12)}</div>
        </OverlayTrigger>
      )
    }, {
      id: 'fee-col',
      Header: 'Fee',
      accessor: 'txn',
      Cell: ({ value }: { value: Transaction }) => {
        const fee = Number(value.txParams.gasPrice) * value.txParams.receipt!.cumulative_gas
        return <OverlayTrigger placement='top'
          overlay={<Tooltip id={'fee-tt'}>{qaToZil(fee)}</Tooltip>}>
          <div className='text-center sm' >{qaToZil(fee, 4)}</div>
        </OverlayTrigger>
      }
    }], []
  )

  const fetchData = useCallback(({ pageIndex }) => {
    if (!dataService) return

    const fetchId = ++fetchIdRef.current
    let txnHashes: string[] | null
    let txnList: TxList
    let txnBodies: TransactionDetails[]
    const getData = async () => {
      try {
        setIsLoading(true)
        txnHashes = recentTxnHashes
        if (!txnHashes) {
          txnList = await dataService.getRecentTransactions()
          if (!txnList) return
          txnHashes = txnList.TxnHashes
          setPageCount(Math.ceil(txnList.number / 10))
          setRecentTxnHashes(txnHashes)
        }

        const slicedTxnHashes = txnHashes.slice(pageIndex * 10, pageIndex * 10 + 10)
        if (slicedTxnHashes) {
          txnBodies = await dataService.getTransactionsDetails(slicedTxnHashes)
          if (txnBodies)
            setData(txnBodies)
        }
      } catch (e) {
        console.log(e)
      } finally {
        setIsLoading(false)
      }
    }

    if (fetchId === fetchIdRef.current)
      getData()
    // Recent transaction hashes is not changed after the initial fetch, until the user refreshes/re-render the component
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [dataService])

  return (
    <>
      {<div>
        <h2>Recent Transactions</h2>
        <ViewAllTable
          columns={columns}
          data={data ? data : []}
          isLoading={isLoading}
          fetchData={fetchData}
          pageCount={pageCount}
        />
      </div>}
    </>
  )
}
Example #18
Source File: ValTxnList.tsx    From devex with GNU General Public License v3.0 4 votes vote down vote up
ValTxnList: React.FC = () => {

  const networkContext = useContext(NetworkContext)
  const { dataService, networkUrl } = networkContext!

  useEffect(() => { setData(null) }, [networkUrl])

  const [data, setData] = useState<TransactionDetails[] | null>(null)

  const columns = useMemo(
    () => [{
      id: 'from-col',
      Header: 'From',
      accessor: 'txn.senderAddress',
      Cell: ({ value }: { value: string }) => (
        <QueryPreservingLink to={`/address/${hexAddrToZilAddr(value)}`}>
          {hexAddrToZilAddr(value)}
        </QueryPreservingLink>)
    }, {
      id: 'to-col',
      Header: 'To',
      Cell: ({ row }: { row: Row<TransactionDetails> }) => {
        return <ToAddrDisp txnDetails={row.original} />
      }
    }, {
      id: 'hash-col',
      Header: 'Hash',
      accessor: 'hash',
      Cell: ({ row }: { row: Row<TransactionDetails> }) => {
        return <QueryPreservingLink to={`/tx/0x${row.original.hash}`}>
          <div className='text-right mono'>
            {row.original.txn.txParams.receipt && !row.original.txn.txParams.receipt.success
              && <FontAwesomeIcon className='mr-1' icon={faExclamationCircle} color='red' />
            }
            {'0x' + row.original.hash}
          </div>
        </QueryPreservingLink>
      }
    }, {
      id: 'amount-col',
      Header: 'Amount',
      accessor: 'txn.amount',
      Cell: ({ value }: { value: string }) => (
        <OverlayTrigger placement='right'
          overlay={<Tooltip id={'amt-tt'}>{qaToZil(value)}</Tooltip>}>
          <div className='text-right sm'>{qaToZil(value, 13)}</div>
        </OverlayTrigger>
      )
    }, {
      id: 'fee-col',
      Header: 'Fee',
      accessor: 'txn',
      Cell: ({ value }: { value: Transaction }) => {
        const fee = Number(value.txParams.gasPrice) * value.txParams.receipt!.cumulative_gas
        return <OverlayTrigger placement='top'
          overlay={<Tooltip id={'fee-tt'}>{qaToZil(fee)}</Tooltip>}>
          <div className='text-center sm'>{qaToZil(fee, 4)}</div>
        </OverlayTrigger>
      }
    }], []
  )

  // Fetch Data
  useEffect(() => {
    let isCancelled = false
    if (!dataService) return

    let receivedData: TransactionDetails[]
    const getData = async () => {
      try {
        receivedData = await dataService.getLatest5ValidatedTransactions()
        if (!isCancelled && receivedData)
          setData(receivedData)
      } catch (e) {
        if (!isCancelled)
          console.log(e)
      }
    }
    getData()

    const getDataTimer = setInterval(async () => {
      await getData()
    }, refreshRate)
    return () => {
      isCancelled = true
      clearInterval(getDataTimer)
    }
  }, [networkUrl, dataService])

  return <>
    <Card className='valtxlist-card'>
      <Card.Header>
        <div className='valtxlist-card-header'>
          <span>Transactions</span>
          <QueryPreservingLink to={'/tx'}>View Recent Transactions</QueryPreservingLink>
        </div>
      </Card.Header>
      <Card.Body>
        {data
          ? <DisplayTable columns={columns} data={data} />
          : <Spinner animation="border" role="status" />
        }
      </Card.Body>
    </Card>
  </>
}
Example #19
Source File: TxnDetailsPage.tsx    From devex with GNU General Public License v3.0 4 votes vote down vote up
TxnDetailsPage: React.FC = () => {
  const { txnHash } = useParams();
  const networkContext = useContext(NetworkContext);
  const { dataService, networkUrl } = networkContext!;

  const [isLoading, setIsLoading] = useState<boolean>(false);
  const [error, setError] = useState<string | null>(null);
  const [data, setData] = useState<TransactionDetails | null>(null);

  // Fetch data
  useEffect(() => {
    if (!dataService) return;

    let receivedData: TransactionDetails;
    const getData = async () => {
      try {
        setIsLoading(true);
        receivedData = await dataService.getTransactionDetails(txnHash);
        if (receivedData) {
          setData(receivedData);
        }
      } catch (e) {
        console.log(e);
        setError(e);
      } finally {
        setIsLoading(false);
      }
    };

    getData();
    return () => {
      setData(null);
      setError(null);
    };
  }, [dataService, txnHash]);

  return (
    <>
      {isLoading ? (
        <div className="center-spinner">
          <Spinner animation="border" />
        </div>
      ) : null}
      {error ? (
        <NotFoundPage />
      ) : (
        data &&
        data.txn.txParams.receipt && (
          <>
            <div className="transaction-header">
              <h3 className="mb-1">
                <span className="mr-1">
                  {data.txn.txParams.receipt.success === undefined ||
                  data.txn.txParams.receipt.success ? (
                    <FontAwesomeIcon color="green" icon={faExchangeAlt} />
                  ) : (
                    <FontAwesomeIcon color="red" icon={faExclamationCircle} />
                  )}
                </span>
                <span className="ml-2">Transaction</span>
                <LabelStar type="Transaction" />
              </h3>
              <ViewBlockLink
                network={networkUrl}
                type="tx"
                identifier={data.hash}
              />
            </div>
            <div className="subtext">
              <HashDisp hash={"0x" + data.hash} />
            </div>
            <Card className="txn-details-card">
              <Card.Body>
                <Container>
                  <Row>
                    <Col>
                      <div className="txn-detail">
                        <span>From:</span>
                        <span>
                          <QueryPreservingLink
                            to={`/address/${hexAddrToZilAddr(
                              data.txn.senderAddress
                            )}`}
                          >
                            {hexAddrToZilAddr(data.txn.senderAddress)}
                          </QueryPreservingLink>
                        </span>
                      </div>
                    </Col>
                    <Col>
                      <div className="txn-detail">
                        <span>To:</span>
                        <span>
                          {data.contractAddr ? (
                            data.txn.txParams.receipt.success ? (
                              <QueryPreservingLink
                                to={`/address/${hexAddrToZilAddr(
                                  data.contractAddr
                                )}`}
                              >
                                <FontAwesomeIcon
                                  color="darkturquoise"
                                  icon={faFileContract}
                                />{" "}
                                {hexAddrToZilAddr(data.contractAddr)}
                              </QueryPreservingLink>
                            ) : (
                              <QueryPreservingLink
                                to={`/address/${hexAddrToZilAddr(
                                  data.txn.txParams.toAddr
                                )}`}
                              >
                                {hexAddrToZilAddr(data.txn.txParams.toAddr)}
                              </QueryPreservingLink>
                            )
                          ) : (
                            <QueryPreservingLink
                              to={`/address/${hexAddrToZilAddr(
                                data.txn.txParams.toAddr
                              )}`}
                            >
                              {hexAddrToZilAddr(data.txn.txParams.toAddr)}
                            </QueryPreservingLink>
                          )}
                        </span>
                      </div>
                    </Col>
                  </Row>
                  <Row>
                    <Col>
                      <div className="txn-detail">
                        <span>Amount:</span>
                        <span>
                          {qaToZil(data.txn.txParams.amount.toString())}
                        </span>
                      </div>
                    </Col>
                    <Col>
                      <div className="txn-detail">
                        <span>Nonce:</span>
                        <span>{data.txn.txParams.nonce}</span>
                      </div>
                    </Col>
                  </Row>
                  <Row>
                    <Col>
                      <div className="txn-detail">
                        <span>Gas Limit:</span>
                        <span>{data.txn.txParams.gasLimit.toString()}</span>
                      </div>
                    </Col>
                    <Col>
                      <div className="txn-detail">
                        <span>Gas Price:</span>
                        <span>
                          {qaToZil(data.txn.txParams.gasPrice.toString())}
                        </span>
                      </div>
                    </Col>
                  </Row>
                  <Row>
                    <Col>
                      <div className="txn-detail">
                        <span>Transaction Fee:</span>
                        <span>
                          {qaToZil(
                            Number(data.txn.txParams.gasPrice) *
                              data.txn.txParams.receipt!.cumulative_gas
                          )}
                        </span>
                      </div>
                    </Col>
                    <Col>
                      <div className="txn-detail">
                        <span>Transaction Block:</span>
                        <span>
                          <QueryPreservingLink
                            to={`/txbk/${data.txn.txParams.receipt.epoch_num}`}
                          >
                            {data.txn.txParams.receipt.epoch_num}
                          </QueryPreservingLink>
                        </span>
                      </div>
                    </Col>
                  </Row>
                  <Row>
                    <Col>
                      <div className="txn-detail">
                        <span>Success:</span>
                        <span>{`${data.txn.txParams.receipt.success}`}</span>
                      </div>
                    </Col>
                    {data.txn.txParams.receipt.accepted !== undefined && (
                      <Col>
                        <div className="txn-detail">
                          <span>Accepts $ZIL:</span>
                          <span>{`${data.txn.txParams.receipt.accepted}`}</span>
                        </div>
                      </Col>
                    )}
                  </Row>
                </Container>
              </Card.Body>
            </Card>
            <TransactionFlow hash={txnHash} txn={data.txn} />
            <InfoTabs tabs={generateTabsFromTxnDetails(data)} />
          </>
        )
      )}
    </>
  );
}
Example #20
Source File: TxBlockDetailsPage.tsx    From devex with GNU General Public License v3.0 4 votes vote down vote up
TxBlockDetailsPage: React.FC = () => {

  const { blockNum } = useParams()
  const networkContext = useContext(NetworkContext)
  const { dataService, isIsolatedServer } = networkContext!

  const [error, setError] = useState<string | null>(null)
  const [isLoading, setIsLoading] = useState(false)
  const [isLoadingTrans, setIsLoadingTrans] = useState(false)
  const [txBlockObj, setTxBlockObj] = useState<TxBlockObj | null>(null)
  const [txBlockTxns, setTxBlockTxns] = useState<string[] | null>(null)
  const [latestTxBlockNum, setLatestTxBlockNum] = useState<number | null>(null)
  const [transactionData, setTransactionData] = useState<TransactionDetails[] | null>(null)

  // Fetch data
  useEffect(() => {
    setIsLoading(true)
    if (!dataService || isIsolatedServer === null) return

    let latestTxBlockNum: number
    let txBlockObj: TxBlockObj
    let txBlockTxns: string[]
    const getData = async () => {
      try {
        if (isNaN(blockNum))
          throw new Error('Not a valid block number')
        if (isIsolatedServer) {
          txBlockTxns = await dataService.getISTransactionsForTxBlock(parseInt(blockNum))
          latestTxBlockNum = await dataService.getISBlockNum()
        } else {
          txBlockObj = await dataService.getTxBlockObj(parseInt(blockNum))
          latestTxBlockNum = await dataService.getNumTxBlocks()
          try {
            txBlockTxns = await dataService.getTransactionsForTxBlock(parseInt(blockNum))
          } catch (e) { console.log(e) }
        }
        if (txBlockObj)
          setTxBlockObj(txBlockObj)
        if (txBlockTxns)
          setTxBlockTxns(txBlockTxns)
        if (latestTxBlockNum)
          setLatestTxBlockNum(latestTxBlockNum)
      } catch (e) {
        console.log(e)
        setError(e)
      } finally {
        setIsLoading(false)
      }
    }

    getData()
    return () => {
      setTxBlockObj(null)
      setTxBlockTxns(null)
      setLatestTxBlockNum(null)
      setError(null)
    }
  }, [blockNum, dataService, isIsolatedServer])

  const columns = useMemo(
    () => [{
      id: 'from-col',
      Header: 'From',
      accessor: 'txn.senderAddress',
      Cell: ({ value }: { value: string }) => (
        <QueryPreservingLink to={`/address/${hexAddrToZilAddr(value)}`}>
          {hexAddrToZilAddr(value)}
        </QueryPreservingLink>
      )
    }, {
      id: 'to-col',
      Header: 'To',
      Cell: ({ row }: { row: Row<TransactionDetails> }) => {
        return <ToAddrDisp txnDetails={row.original} />
      }
    }, {
      id: 'hash-col',
      Header: 'Hash',
      Cell: ({ row }: { row: Row<TransactionDetails> }) => {
        console.log(row)
        return <QueryPreservingLink to={`/tx/0x${row.original.hash}`}>
          <div className='text-right mono'>
            {row.original.txn.txParams.receipt && !row.original.txn.txParams.receipt.success
              && <FontAwesomeIcon className='mr-1' icon={faExclamationCircle} color='red' />
            }
            {'0x' + row.original.hash}
          </div>
        </QueryPreservingLink>
      }
    }, {
      id: 'amount-col',
      Header: 'Amount',
      accessor: 'txn.amount',
      Cell: ({ value }: { value: string }) => (
        <OverlayTrigger placement='right'
          overlay={<Tooltip id={'amt-tt'}>{qaToZil(value)}</Tooltip>}>
          <div className='text-right'>{qaToZil(value, 10)}</div>
        </OverlayTrigger>
      )
    }, {
      id: 'fee-col',
      Header: 'Fee',
      accessor: 'txn',
      Cell: ({ value }: { value: Transaction }) => {
        const fee = Number(value.txParams.gasPrice) * value.txParams.receipt!.cumulative_gas
        return <OverlayTrigger placement='top'
          overlay={<Tooltip id={'fee-tt'}>{qaToZil(fee)}</Tooltip>}>
          <div className='text-center'>{qaToZil(fee, 4)}</div>
        </OverlayTrigger>
      }
    }], []
  )
  const fetchData = useCallback(({ pageIndex }) => {

    if (!txBlockTxns || !dataService) return

    let receivedData: TransactionDetails[]
    const getData = async () => {
      try {
        setIsLoadingTrans(true)
        receivedData = await dataService.getTransactionsDetails(txBlockTxns.slice(pageIndex * 10, pageIndex * 10 + 10))
        if (receivedData)
          setTransactionData(receivedData)
      } catch (e) {
        console.log(e)
      } finally {
        setIsLoadingTrans(false)
      }
    }

    getData()
  }, [dataService, txBlockTxns])

  return <>
    {isLoading ? <div className='center-spinner'><Spinner animation="border" /></div> : null}
    {error
      ? <NotFoundPage />
      : <>
        {latestTxBlockNum &&
          <div className={isIsolatedServer ? 'txblock-header mb-3' : 'txblock-header'}>
            <h3 className='mb-1'>
              <span className='mr-1'>
                <FontAwesomeIcon className='fa-icon' icon={faCubes} />
              </span>
              <span className='ml-2'>
                Tx Block
              </span>
              {' '}
              <span className='subtext'>#{blockNum}</span>
              <LabelStar type='Tx Block' />
            </h3>
            <span>
              <QueryPreservingLink
                className={
                  isIsolatedServer
                    ? parseInt(blockNum, 10) === 1 ? 'disabled mr-3' : 'mr-3'
                    : parseInt(blockNum, 10) === 0 ? 'disabled mr-3' : 'mr-3'}
                to={`/txbk/${parseInt(blockNum, 10) - 1}`}>
                <FontAwesomeIcon size='2x' className='fa-icon' icon={faCaretSquareLeft} />
              </QueryPreservingLink>
              <QueryPreservingLink
                className={
                  isIsolatedServer
                    ? parseInt(blockNum, 10) === latestTxBlockNum ? 'disabled' : ''
                    : parseInt(blockNum, 10) === latestTxBlockNum - 1 ? 'disabled' : ''}
                to={`/txbk/${parseInt(blockNum, 10) + 1}`}>
                <FontAwesomeIcon size='2x' className='fa-icon' icon={faCaretSquareRight} />
              </QueryPreservingLink>
            </span>
          </div>
        }
        {txBlockObj && (
          <>
            <div className='subtext'>
              <HashDisp hash={'0x' + txBlockObj.body.BlockHash} />
            </div>
            <Card className='txblock-details-card'>
              <Card.Body>
                <Container>
                  <BRow>
                    <BCol>
                      <div className='txblock-detail'>
                        <span>Date:</span>
                        <span>
                          {timestampToDisplay(txBlockObj.header.Timestamp)}
                          {' '}
                        ({timestampToTimeago(txBlockObj.header.Timestamp)})
                      </span>
                      </div>
                    </BCol>
                    <BCol>
                      <div className='txblock-detail'>
                        <span>Transactions:</span>
                        <span>{txBlockObj.header.NumTxns}</span>
                      </div>
                    </BCol>
                  </BRow>
                  <BRow>
                    <BCol>
                      <div className='txblock-detail'>
                        <span>Gas Limit:</span>
                        <span>{txBlockObj.header.GasLimit}</span>
                      </div>
                    </BCol>
                    <BCol>
                      <div className='txblock-detail'>
                        <span>Gas Used:</span>
                        <span>{txBlockObj.header.GasUsed}</span>
                      </div>
                    </BCol>
                  </BRow>
                  <BRow>
                    <BCol>
                      <div className='txblock-detail'>
                        <span>Txn Fees:</span>
                        <span>{qaToZil(txBlockObj.header.TxnFees)}</span>
                      </div>
                    </BCol>
                    <BCol>
                      <div className='txblock-detail'>
                        <span>Rewards Fees:</span>
                        <span>{qaToZil(txBlockObj.header.Rewards)}</span>
                      </div>
                    </BCol>
                  </BRow>
                  <BRow>
                    <BCol>
                      <div className='txblock-detail'>
                        <span>DS Block:</span>
                        <span><QueryPreservingLink to={`/dsbk/${txBlockObj.header.DSBlockNum}`}>{txBlockObj.header.DSBlockNum}</QueryPreservingLink></span>
                      </div>
                    </BCol>
                    <BCol>
                      <div className='txblock-detail'>
                        <span>DS Leader:</span>
                        <span><QueryPreservingLink to={`/address/${pubKeyToZilAddr(txBlockObj.header.MinerPubKey)}`}>{pubKeyToZilAddr(txBlockObj.header.MinerPubKey)}</QueryPreservingLink></span>
                      </div>
                    </BCol>
                  </BRow>
                </Container>
              </Card.Body>
            </Card>
            {txBlockObj.body.MicroBlockInfos.length > 0 && (
              <Card className='txblock-details-card mono'>
                <Card.Body>
                  <Container>
                    <span>Micro Blocks</span>
                    {txBlockObj.body.MicroBlockInfos
                      .map((x) => (
                        <div key={x.MicroBlockHash}>[{x.MicroBlockShardId}] {x.MicroBlockHash}</div>
                      ))}
                  </Container>
                </Card.Body>
              </Card>
            )}
          </>
        )}
        {txBlockTxns && txBlockTxns.length > 0 && (
          <>
            <h4>Transactions</h4>
            <ViewAllTable
              isLoading={isLoadingTrans}
              fetchData={fetchData}
              pageCount={Math.ceil(txBlockTxns.length / 10)}
              columns={columns}
              data={transactionData ? transactionData : []} />
          </>
        )}
      </>
    }
  </>
}
Example #21
Source File: TransactionsCard.tsx    From devex with GNU General Public License v3.0 4 votes vote down vote up
TransactionsCard: React.FC<IProps> = ({
  transactions: txs,
  addr,
  fungibleToken,
}) => {
  const transactions: any[] = txs.flatMap(
    (tx: { receipt: { transitions: [] } }) => {
      if (fungibleToken && tx.receipt.transitions.length) {
        console.log(tx.receipt.transitions);
        const tokenTx: any | undefined = tx.receipt.transitions.find(
          (transition: { msg: { _tag: string } }) =>
            transition.msg._tag === "TransferSuccessCallBack"
        );

        if (tokenTx !== undefined) {
          const toAddr = tokenTx.msg.params.find(
            (p: any) => p.vname === "recipient"
          );
          const fromAddr = tokenTx.msg.params.find(
            (p: any) => p.vname === "sender"
          );
          const amount = tokenTx.msg.params.find(
            (p: any) => p.vname === "amount"
          );
          return [
            { ...tx },
            {
              ...tx,
              ID: "token-transfer",
              toAddr: toAddr.value,
              fromAddr: fromAddr.value,
              amount: amount.value,
              type: "token-transfer",
              fungibleToken,
            },
          ];
        }
      }
      return {
        ...tx,
      };
    }
  );

  const columns = useMemo(
    () => [
      {
        id: "alert-col",
        Header: "",
        Cell: ({ row }: { row: any }) => {
          return (
            <div className="d-flex align-items-center justify-content-start">
              {row.original.receipt && !row.original.receipt.success && (
                <FontAwesomeIcon icon={faExclamationCircle} color="red" />
              )}
              <AgeDisplay className="ml-2" timestamp={row.original.timestamp} />
            </div>
          );
        },
      },
      {
        id: "hash-col",
        Header: "Hash",
        accessor: "hash",
        Cell: ({ row }: { row: any }) => {
          return row.original.ID !== "token-transfer" ? (
            <QueryPreservingLink
              to={`/tx/0x${row.original.ID}`}
              className="d-flex"
            >
              <div className="text-right mono ellipsis">
                {"0x" + row.original.ID}
              </div>
            </QueryPreservingLink>
          ) : (
            `${fungibleToken.name.value} Transfer`
          );
        },
      },
      {
        id: "from-col",
        Header: "From",
        accessor: "fromAddr",
        Cell: ({ value }: { value: string }) => {
          const ziladdr = hexAddrToZilAddr(value);
          return (
            <>
              {addr === ziladdr ? (
                <span className="text-muted">{addr}</span>
              ) : (
                <QueryPreservingLink to={`/address/${ziladdr}`}>
                  {ziladdr}
                </QueryPreservingLink>
              )}
            </>
          );
        },
      },
      {
        id: "type-col",
        Header: "",
        Cell: ({ row }: { row: any }) => {
          console.log(row.original);
          return (
            <>
              <TypeDisplay
                fromAddr={row.original.fromAddr}
                toAddr={row.original.toAddr}
                addr={addr}
                type={row.original.type}
              />
            </>
          );
        },
      },
      {
        id: "to-col",
        Header: "To",
        Cell: ({ row }: { row: any }) => {
          return (
            <ToAddrDispSimplified
              fromAddr={row.original.fromAddr}
              toAddr={row.original.toAddr}
              txType={row.original.type}
              addr={addr}
            />
          );
        },
      },
      {
        id: "amount-col",
        Header: "Amount",
        Cell: ({ row }: any) => {
          const value = row.original.amount;
          let formattedValue: string =
            numbro(qaToZilSimplified(value)).format({
              thousandSeparated: true,
              mantissa: 3,
            }) + " ZIL";

          if (row.original.ID === "token-transfer") {
            formattedValue =
              value / Math.pow(10, parseInt(fungibleToken.decimals.value)) +
              ` ${fungibleToken.symbol.value}`;
          }
          return (
            <OverlayTrigger
              placement="top"
              overlay={
                <Tooltip id={"amt-tt"}>
                  {numbro(value).format({ thousandSeparated: true })}
                </Tooltip>
              }
            >
              <div className="text-right sm">{formattedValue}</div>
            </OverlayTrigger>
          );
        },
      },
      {
        id: "fee-col",
        Header: "Fee",
        Cell: ({ row }: any) => {
          const fee =
            parseFloat(row.original.receipt.cumulative_gas) *
            row.original.gasPrice;
          return (
            <OverlayTrigger
              placement="top"
              overlay={<Tooltip id={"fee-tt"}>{fee} Qa</Tooltip>}
            >
              <div className="text-center sm">{qaToZil(fee, 4)}</div>
            </OverlayTrigger>
          );
        },
      },
    ],
    [addr]
  );

  return (
    <div>
      <DisplayTable columns={columns} data={transactions} />
    </div>
  );
}
Example #22
Source File: Images.tsx    From frontend.ro with MIT License 4 votes vote down vote up
export default function ImagesContent() {
  const lessonInfo = getLessonById('imagini');

  return (
    <>
      <LessonCover>
        <Image
          width="2400"
          height="1260"
          alt="Doodle cu rama unei imagini"
          src={`${process.env.CLOUDFRONT_PUBLIC}/public/seo/html-images_2400w.jpg`}
        />
      </LessonCover>
      <LessonFirstSentence>
        Cred că putem cu toții admite că un Internet fără imagini ar
        fi destul de plictisitor. Deci hai să
        încheiem așteptarea și să vedem cum adăugăm imagini
        în site-urile noastre și care sunt cele mai bune practici legate de acest subiect.
      </LessonFirstSentence>
      <section>
        <LessonHeading as="h2" id={lessonInfo.chapters[0].id}>
          {lessonInfo.chapters[0].title}
        </LessonHeading>
        <p>
          Primul și cel mai comun mod de a adăuga o imagine este folosind elementul
          {' '}
          <strong>img</strong>
          {' '}
          alături de 2 atribute:
        </p>
        <List variant="bullets">
          <li>
            <strong>src</strong>
            : pentru a specifica URL-ul imaginii
          </li>
          <li>
            <strong>alt</strong>
            : pentru a descrie conținutul imaginii - în caz că aceasta nu poate fi încărcată
          </li>
        </List>
        <Highlight
          className="my-5"
          language="html"
          code={`
<img 
  src="golden-retrieve-and-ball.jpg" 
  alt="Golden retriever biting blue ball"
/>`}
        />
        <p>
          Dacă imaginea se află la acel URL și avem conexiune la internet
          vom obține o pagină ca în imaginea din stânga. Însă, dacă
          browserul nu a putut încărca imaginea, vom vedea descrierea
          text ca în exemplul din dreapta.
        </p>
        <SideBySidePictures
          img1={{
            src: 'https://d3tycb976jpudc.cloudfront.net/demo-assets/golden-retriever-and-ball.jpg',
            alt: 'Imagine încărcată cu succes într-o pagină Web',
            demo: '/demo/html/imagine-incarcata-cu-succes',
          }}
          img2={{
            src: '/images/lessons/images/image-alt.png',
            alt: 'Descrierea text a imaginii, dacă aceasta nu a putut fi încărcată',
            demo: '/demo/html/text-alternativ-imagine',
          }}
        />
        <p>
          Este foarte important să nu uităm de atributul
          {' '}
          <strong>alt</strong>
          .
          Pe lângă cazul menționat mai sus, acesta ajută și persoanele cu dizabilități
          ce consumă conținut Web via screen readere.
          {/* Uite un demo folosind progamul XXX? */}
        </p>

        {/* TODO: add audio demo */}
        {/* <AudioPlayer className="my-5" src="" title="Web captions demo" /> */}

        <LessonTip>
          De aceea, o pagină ce conține imagini
          {' '}
          <strong>fără atributul alt</strong>
          {' '}
          nu este
          {' '}
          <Link href="/html/validarea-paginilor-html">
            <a>
              considerată validă
            </a>
          </Link>
          .
        </LessonTip>
      </section>
      <section>
        <LessonHeading as="h2" id={lessonInfo.chapters[1].id}>
          {lessonInfo.chapters[1].title}
        </LessonHeading>
        <p>
          De multe ori imaginile de pe site-uri își vor adapta dimensiunea în funcție de
          ecranul dispozitivului folosit: mai mici pe telefoane și tablete, mai mari
          pe laptop-uri și desktop-uri.
        </p>
        <p>
          În galeria de mai jos avem 2 imagini pe fiecare rând, fiecare ocupând
          exact 45% din lățimea ecranului - indiferent care e aceasta.
        </p>
        <LessonFigure
          withBorder
          src="https://d3tycb976jpudc.cloudfront.net/demo-assets/fixed-gallery.png"
          alt="Galerie cu 2 imagini pe fiecare rând"
          demo="/demo/html/galerie-imagini-fixa"
        />
        <p>
          Însă există situații în care o imagine va avea aceleași dimensiuni fixe indiferent
          de dispozitiv. Un exemplu ar putea fi logo-ul unei companii
          aflat de obicei în
          {' '}
          <FormattedText as="strong">{'<header>'}</FormattedText>
          {' '}
          -ul paginii.
        </p>
        <LessonFigure
          withBorder
          src="https://d3tycb976jpudc.cloudfront.net/demo-assets/fixed-size-image.png"
          alt="Imagine cu dimensiunile fixe"
          demo="/demo/html/imagine-cu-dimensiuni-fixe"
        />
        <p>
          În astfel de cazuri, în care
          {' '}
          <strong>știm dinainte dimensiunea</strong>
          {' '}
          , este recomandat
          să adăugam și atributele
          <strong> width </strong>
          și
          {' '}
          <strong> height </strong>
          .
        </p>
        <Highlight
          className="my-5"
          language="html"
          code={`
<img 
  src="logo.png" 
  width="212" 
  height="70"
  alt="FrontEnd.ro logo" 
/>`}
        />
        <p>
          Astfel, browser-ul va ști dimensiunile imaginii înainte de a o descărca
          iar experiența utilizatorilor va fi extrem de fluidă. Astfel evităm
          situația de mai jos unde textul se re-aranjează după încărcarea imaginii
          - problemă cunoscută sub numele de
          {' '}
          <strong>content/layout shifting</strong>
          .
        </p>
        <LessonFigure
          isVideo
          withBorder
          src="https://d3tycb976jpudc.cloudfront.net/demo-assets/content-shift.mp4"
          alt="Conținutul text se re-aranjează după încărcarea imaginii"
          demo="/demo/html/content-shifting"
        />
      </section>
      <div className="dots" />
      <section>
        <p>
          Ce am arătat până acum reprezintă fundamentele imaginilor în HTML.
          Sunt lucrurile pe care le vei folosi în marea majoritate a cazurilor...
        </p>

        <p>
          Mai jos continuăm să discutăm despre diverse tehnici
          pentru a optimiza servirea imaginilor și a oferi cea mai
          bună experiență posibilă, care la randul ei va mări
          șansele ca
          {' '}
          <a href="https://web.dev/site-speed-and-business-metrics/" target="_blank" rel="noreferrer">
            proiectul/business-ul nostru să aibă succes.
          </a>
          .
        </p>
      </section>
      <section>
        <LessonHeading as="h2" id={lessonInfo.chapters[2].id}>
          {lessonInfo.chapters[2].title}
        </LessonHeading>
        <LessonQuote>
          Most of the images on the web are downloaded,
          decoded and rendered only never to be seen, as [...]
          the user never scrolled that far.
          -
          {' '}
          <small>
            <a href="https://twitter.com/yoavweiss" target="_blank" rel="noopener noreferrer">Yoav Weiss</a>
          </small>
        </LessonQuote>
        <p>
          Citatul de mai sus a rămas - din păcate - la fel de adevărat...
          De câte ori nu ai deschis o pagină Web și apoi ai ieșit de acolo fără
          a citi mai mult de primul paragraf?
        </p>
        <p>
          În background însă, browser-ul a încărcat toate imaginile,
          ceea ce e o risipă pentru că noi nu le-am văzut pe toate.
          Ideal ar fi să încărcăm imaginile
          <strong>
            {' '}
            doar atunci când avem nevoie de ele
            {' '}
          </strong>
          .
          {/* , */}
          {/* cum se întâmplă în video-ul din dreapta. */}
        </p>
        {/* TODO: video demo */}
        {/* <h1> VIDEO DEMO </h1> */}
        <p>
          Pentru a rezolva această problemă vom
          folosi atributul
          {' '}
          <strong>loading</strong>
          {' '}
          și valoarea
          {' '}
          <strong>lazy</strong>
          .
        </p>
        <Highlight
          className="my-5"
          language="html"
          code={`
<img 
  alt="Eiffel tower during night"
  src="eiffel-tower-during-night.jpg"
  loading="lazy"
/>
`}
        />
        <p>
          Acum browserul va descărca imaginea doar când ne
          <em>"apropiem"</em>
          {' '}
          de ea. Fiecare browser are propriile metrici legate
          de ce înseamnă această apropiere, însă nu trebuie să ne batem
          capul cu asta. Regula generală e să adăugăm atributul
          {' '}
          <FormattedText as="strong">loading="lazy"</FormattedText>
          {' '}
          dacă avem multe imagini în pagină.
        </p>
      </section>
      <section>
        <LessonHeading as="h2" id={lessonInfo.chapters[3].id}>
          {lessonInfo.chapters[3].title}
        </LessonHeading>
        <p>
          Am pornit de la imagini simple, am optimizat experiența folosind
          atributele width/height iar apoi am reușit să incărcăm doar imaginile
          de care avem nevoie folosind atributul
          {' '}
          <FormattedText as="strong">
            loading
          </FormattedText>
          . Acum e momentul să mergem un pas mai departe în călătoria spre performanță
          și să încărcăm imaginea cea mai potrivită din punct de vedere al rezoluției.
        </p>
        <p>
          De exemplu, să presupunem că avem o imagine care va acoperi întreaga pagină:
        </p>
        <LessonFigure
          withBorder
          src="https://d3tycb976jpudc.cloudfront.net/demo-assets/full-screen-image-illustration.jpg"
          alt="Imagine full-screen intr-o pagină Web"
        />
        <LessonQuote>
          La ce rezoluție salvăm imaginea pentru a fi 100% clară pe toate dispozitivele?
        </LessonQuote>
        <p>
          Știind că site-ul poate fi văzut atât de pe dispozitive mobile,
          cu ecrane mai mici, cât și de pe desktop-uri sau chiar televizoare,
          o primă soluție ar fi să salvăm imaginea la o rezoluție cât mai
          înaltă - să zicem 4K- pentru a ne asigura că totul e ok.
          {/* Pentru imaginea folosita ca demo in acest capitol inseamna o dimensiune de xMB. */}
        </p>
        <p>
          <strong>
            Deși soluția ar funcționa, ea nu e deloc eficientă
          </strong>
          , căci vom încărca
          mereu aceeași imagine de rezoluție înaltă și mulți MBs.
          Pe telefon de exemplu, unde ecranul e mai mic, nu avem nevoie
          de toți cei 8+ milioane de pixeli (3840 x 2160).
          Dacă luăm în calcul și conexiunea la internet, experiența poate arăta așa:
        </p>
        <LessonFigure
          isVideo
          withBorder
          demo="/demo/html/incarcarea-unei-imagini-mari"
          src="https://d3tycb976jpudc.cloudfront.net/demo-assets/huge-image-loading.mp4"
          alt="Încărcarea unei imagini mari pe o conexiune înceată"
        />
        <p>
          Ideal ar fi ca pe telefon să încărcăm exact aceeași imagine dar la o
          rezoluție mai mică, pe tabletă la o rezoluție mijlocie și la o rezoluție
          cât mai inaltă pe ecrane mari: desktop and beyond.
        </p>
        <section>
          <LessonHeading as="h3" id={lessonInfo.chapters[3].subchapters[0].id}>
            {lessonInfo.chapters[3].subchapters[0].title}
          </LessonHeading>
          <p>
            Din fericire putem rezolva această problemă folosindu-ne de atributele
            {' '}
            <strong>srcset</strong>
            {' '}
            și
            {' '}
            <strong>sizes</strong>
            . Hai să luăm imaginea noastră și să facem resize la 3 rezoluții diferite:
          </p>
          <LessonFigure
            withBorder
            src="https://d3tycb976jpudc.cloudfront.net/demo-assets/red_bycicle_resize_infographic.jpg"
            alt="Aceeași imagine în 3 dimensiuni diferite"
          />
          <LessonTip>
            Pe Windows putem face resize cu
            {' '}
            <strong>Paint</strong>
            {' '}
            iar pe MacOS folosind
            {' '}
            <strong>
              <a href="https://support.apple.com/guide/preview/resize-rotate-or-flip-an-image-prvw2015/mac" target="_blank" rel="noreferrer">Preview App</a>
            </strong>
            {' '}
            .
          </LessonTip>
          <p>
            Apoi vom adăuga atributul
            {' '}
            <strong>srcset</strong>
            {' '}
            unde definim diferitele
            surse ale imaginii împreună cu dimensiunea (în lățime) a fiecăreia.
            Iar cu atributul
            <strong>sizes</strong>
            vom specifica ce dimensiuni va avea imaginea
            în funcție de dimensiunea ecranului:
          </p>
          <Highlight
            className="my-5"
            language="html"
            code={`
<img srcset="
    /red_bycicle__high.jpg 4000w,
    /red_bycicle__med.jpg 2000w,
    /red_bycicle__low.jpg 800w,
  " 
  sizes="(max-width: 800px) 700px, 1000px"
  alt="Red bycicle wheel"
  src="/red_bycicle__med.jpg"
/>`}
          />
          {/* <LessonTip icon={faQuestionCircle}>
            Atributul
            {' '}
            <strong>style</strong>
            {' '}
            este folosit pentru a adauga reguli
            CSS elementelor.
            {' '}
            Inca nu am ajuns la acel capitol deci e absolut normal sa nu stii ce face.
            <br />
            {' '}
            <br />
            Totusi, te rugam sa-l pui acolo, impreuna cu valoarea `max-width: 100%.
            Astfel ne asiguram ca imaginile nu vor iesi din pagina.
          </LessonTip> */}
          <p>
            Șiiii voilà. Dacă mergem în
            {' '}
            <a href="/intro/devtools">modul responsive</a>
            {' '}
            - și ținem tabul
            {' '}
            <strong>network</strong>
            {' '}
            deschis vom observa cum diferite surse ale imaginii
            se încarcă la rezoluții diferite.
          </p>
          <LessonFigure
            isVideo
            withBorder
            demo="/demo/html/atributul-srcset"
            src="https://d3tycb976jpudc.cloudfront.net/demo-assets/img-srcset.mp4"
            alt="Diferite surse ale imaginii se încarcă la rezoluții diferite"
          />
          <LessonTip icon={faExclamationCircle}>
            O imagine cu
            {' '}
            <FormattedText as="span">srcset=""</FormattedText>
            {' '}
            trebuie să conțină și atributul
            {' '}
            <FormattedText as="span">src=""</FormattedText>
            {' '}
            pentru a fi validă.
            Motivul îl reprezintă browserele mai vechi
            ce nu înțeleg atributul
            {' '}
            <FormattedText as="span">srcset</FormattedText>
            , așadar îl vor ignora și
            vor arăta imaginea de la
            {' '}
            <FormattedText as="span">src</FormattedText>
            .
          </LessonTip>

          <p>
            Te încurajăm să experimentezi și cu opțiunea
            {' '}
            <Link href="intro/devtools">
              <a target="_blank">
                DRP (Device Pixel Ratio)
              </a>
            </Link>
            {' '}
            De exemplu, cu o valoare
            de 2 și o lățime de 650px se va incărca imaginea
            {' '}
            <strong>red_bycicle__med.jpg</strong>
            .
          </p>
        </section>
        <section>
          <LessonHeading as="h3" id={lessonInfo.chapters[3].subchapters[1].id}>
            {lessonInfo.chapters[3].subchapters[1].title}
          </LessonHeading>
          <p>
            Până de curând singurul mod de a face imaginile din CSS (cele puse prin
            {' '}
            <FormattedText as="strong">background-image:</FormattedText>
            {' '}
            )
            responsive a fost să punem mai multe reguli
            {' '}
            <FormattedText as="strong">@media-query</FormattedText>
            {' '}
            și să specificăm pentru fiecare o altă imagine.
          </p>
          <p>
            Dar odată cu introducerea proprietății
            {' '}
            <a
              target="_blank"
              className="text-bold"
              href="https://developer.mozilla.org/en-US/docs/Web/CSS/image/image-set()"
              rel="noreferrer"
            >
              image-set()
            </a>
            {' '}
            putem lăsa browserul să aleagă cea mai bună imagine în funcție de rezoluția ecranului:
          </p>
          <Highlight
            language="css"
            code={`
.cover {
  height: 50vh;

  background-image: -webkit-image-set(
    url("red_bycicle__low.jpg") 1x,
    url("red_bycicle__med.jpg") 2x, 
    url("red_bycicle__high.jpg") 4x
  );

  background-image: image-set(
    url("red_bycicle__low.jpg") 1x,
    url("red_bycicle__med.jpg") 2x, 
    url("red_bycicle__high.jpg") 4x
  );
}
              `}
          />
          <LessonTip icon={faQuestion}>
            Această proprietate nu are încă suport nativ în toate Browserele,
            așa că în exemplul de mai sus am prefixat regulat cu
            {' '}
            <FormattedText as="span">-webkit</FormattedText>
            {' '}
            pentru a funcționa și în Chrome sau Safari.
          </LessonTip>
        </section>
      </section>
      <section>
        <LessonHeading as="h2" id={lessonInfo.chapters[4].id}>
          {lessonInfo.chapters[4].title}
        </LessonHeading>
        <p>
          După cum ai văzut până acum, elementul
          {' '}
          <FormattedText as="span">{'<img>'}</FormattedText>
          {' '}
          - deși destul de simplu în utilizare - ne oferă mai multe funcționalități
          care ne permit să optimizăm imaginile și experiența utilizatorilor.
          Cu toate acestea, a mai rămas totuși o ultimă optimizare,
          și anume folosirea unor formate moderne pentru imagini.
        </p>
        <LessonQuote>
          De ce am vrea alte formate? Nu sunt
          {' '}
          <strong>JPG</strong>
          {' '}
          sau
          {' '}
          <strong>PNG</strong>
          {' '}
          de ajuns?
        </LessonQuote>
        <p>
          Hmmm.... nu chiar. Există formate mai moderne precum
          {' '}
          <strong>WebP</strong>
          {' '}
          sau
          {' '}
          <strong>AVIF</strong>
          {' '}
          care
          oferă aceeași calitate a imaginii la o dimensiune mai mică.
          Uite diferențele de dimensiune ale acestei imagini în funcție de format:
        </p>
        <LessonTable {...sizesTable} className="my-5" />
        <p>
          După cum vezi formatele WebP și AVIF sunt mai mici decât JPG sau PNG,
          deci imaginile în acest format se vor incărca mai repede decât celelalte.
          Problema este că nu toate browserele înțeleg aceste noi formate.
        </p>
        <p>
          După cum vedem pe
          {' '}
          <a href="https://CanIUse.com" target="_blank" rel="noreferrer">Can I use...</a>
          - AVIF are suport doar în ultimele versiuni
          de Google Chrome în timp ce WebP este mai comun însă tot lipsește din
          IOS 13 sau Internet Explorer 11.
        </p>
        <SideBySidePictures
          direction="column"
          img1={{
            src: 'https://d3tycb976jpudc.cloudfront.net/demo-assets/caniuse-webp.png',
            alt: 'Suportul browserelor pentru formatul WebP',
            demo: 'https://caniuse.com/?search=webp',
          }}
          img2={{
            src: 'https://d3tycb976jpudc.cloudfront.net/demo-assets/caniuse-avif.png',
            alt: 'Suportul browserelor pentru formatul Avif',
            demo: 'https://caniuse.com/?search=avif',
          }}
        />
        <p>
          Deci avem nevoie de o modalitate prin care browsere care înțeleg
          {' '}
          <strong>WebP</strong>
          {' '}
          sau
          {' '}
          <strong> Avif</strong>
          {' '}
          să descarce aceste formate, în timp ce celelalte să rămână la JPG.
          Această tehnică se numește general
          {' '}
          <a href="/concepte/graceful-degradation">graceful degradation</a>
          .
        </p>
        <p>
          Thankfully, această soluție ne este permisă de tag-ul
          {' '}
          <FormattedText as="strong">{'<picture>'}</FormattedText>
          {' '}
          ,
          unde putem specifica mai multe surse pentru o imagine și să lăsăm
          browserul să o aleagă pe cea pe care o înțelege.
        </p>
        <Highlight
          className="my-5"
          language="html"
          code={`
<picture>
  <source 
    srcset="
      red_bycicle__high.avif 4000w, 
      red_bycicle__med.avif 2000w, 
      red_bycicle__low.avif 800w" 
    type="image/avif">

  <source 
    srcset="
      red_bycicle__high.webp 4000w, 
      red_bycicle__med.webp 2000w, 
      red_bycicle__low.webp 800w"
    type="image/webp">

  <source 
    srcset="
      red_bycicle__high.jpg 4000w, 
      red_bycicle__med.jpg 2000w, 
      red_bycicle__low.jpg 800w" 
    type="image/jpeg">

  <img 
    alt="Red bycicle wheel"
    loading="lazy"
    srcset="
      red_bycicle__high.jpg 4000w, 
      red_bycicle__med.jpg 2000w, 
      red_bycicle__low.jpg 800w
    "
    src="red_bycicle__med.jpg"
  >
</picture>`}
        />
      </section>
      <p>
        Ordinea elementelor
        {' '}
        <FormattedText as="strong">{'<source>'}</FormattedText>
        {' '}
        este extrem de importantă căci browserul le va parcurge
        de sus-in-jos și o va alege pe prima compatibilă. De asemenea,
        în fiecare dintre ele trebuie adăugat - via atributul
        {' '}
        <strong>srcset</strong>
        {' '}
        - mai multe surse de dimensiuni diferite.
        Astfel browserul nu alege numai formatul cel mai bun, cât și
        dimensiunea optimă a imaginii. Best of both worlds! ?
      </p>
      <p>
        PS: poate ai observat acel ultim
        {' '}
        <FormattedText as="strong">{'<img>'}</FormattedText>
        {' '}
        tag. Ei bine, avem nevoie de el pentru
        a specifica descrierea imaginii - în caz că aceasta nu poate fi încărcată,
        cât și pentru eventuala adaugare a unor atribute extra - cum ar fi
        {' '}
        <strong>loading</strong>
        . Iar în cazurile mai rare în care utilizatorii folosesc browsere
        destul de vechi, ce nu înțeleg elementul
        {' '}
        <FormattedText as="strong">{'<picture>'}</FormattedText>
        {' '}
        , acestea vor înțelege
        totuși tag-ul
        {' '}
        <FormattedText as="strong">{'<img>'}</FormattedText>
        {' '}
        și îl vor arăta pe acesta.
      </p>
      <div className="dots" />
      <LessonResources
        className="my-5"
        links={[{
          text: 'Documentația completă a elementului <img> pe MDN',
          url: 'https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img',
        }, {
          text: 'Documentația completă a elementului <picture> pe MDN',
          url: 'https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture',
        }, {
          text: 'Mai multe detalii despre imagini Responsive',
          url: 'https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture',
        },
        ]}
      />
    </>
  );
}