@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 |
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 |
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 |
/**
* 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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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',
},
]}
/>
</>
);
}