utils#isAddress JavaScript Examples
The following examples show how to use
utils#isAddress.
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: index.js From Artion-Client with GNU General Public License v3.0 | 4 votes |
AccountDetails = () => {
const dispatch = useDispatch();
const {
storageUrl,
getUserAccountDetails,
getUserFigures,
fetchCollections,
fetchTokens,
updateBanner,
getAccountActivity,
getActivityFromOthers,
getMyOffers,
getFollowing,
followUser: _followUser,
getFollowers,
getFollowings,
getMyLikes,
getItemsLiked,
} = useApi();
const { getTokenByAddress } = useTokens();
const { account, chainId } = useWeb3React();
const { width, ref } = useResizeDetector();
const { uid } = useParams();
const { authToken } = useSelector(state => state.ConnectWallet);
const { user: me } = useSelector(state => state.Auth);
const fileInput = useRef();
const [anchorEl, setAnchorEl] = useState(null);
const [prevUID, setPrevUID] = useState(null);
const [bundleModalVisible, setBundleModalVisible] = useState(false);
const [followingsModalVisible, setFollowingsModalVisible] = useState(false);
const [followersModalVisible, setFollowersModalVisible] = useState(false);
const [fetching, setFetching] = useState(false);
const [bundleFetching, setBundleFetching] = useState(false);
const [favFetching, setFavFetching] = useState(false);
const [fguresFetching, setFiguresFetching] = useState(false);
const tokens = useRef([]);
const bundles = useRef([]);
const likes = useRef([]);
const [followersLoading, setFollowersLoading] = useState(false);
const followers = useRef([]);
const followings = useRef([]);
const [following, setFollowing] = useState(false);
const [followingInProgress, setFollowingInProgress] = useState(false);
const [count, setCount] = useState(0);
const [bundleCount, setBundleCount] = useState(0);
const [favCount, setFavCount] = useState(0);
const [now, setNow] = useState(new Date());
const [page, setPage] = useState(0);
const [bannerHash, setBannerHash] = useState();
const [loading, setLoading] = useState(true);
const [user, setUser] = useState({});
const [copied, setCopied] = useState(false);
const [tooltipOpen, setTooltipOpen] = useState(false);
const [tab, setTab] = useState(0);
const [activityLoading, setActivityLoading] = useState(false);
const [activities, setActivities] = useState([]);
const [bidsLoading, setBidsLoading] = useState(false);
const [bids, setBids] = useState([]);
const [offersLoading, setOffersLoading] = useState(false);
const [offers, setOffers] = useState([]);
const [fetchInterval, setFetchInterval] = useState(null);
const [likeCancelSource, setLikeCancelSource] = useState(null);
const [prevNumPerRow, setPrevNumPerRow] = useState(null);
const prevAuthToken = usePrevious(authToken);
const numPerRow = Math.floor(width / 240);
const fetchCount = numPerRow <= 3 ? 18 : numPerRow === 4 ? 16 : numPerRow * 3;
const getUserDetails = async _account => {
setLoading(true);
try {
const { data } = await getUserAccountDetails(_account);
setUser(data);
} catch {
setUser({});
}
try {
const { data: isFollowing } = await getFollowing(account, _account);
if (account === undefined) {
setFollowing(false);
} else {
setFollowing(isFollowing);
}
} catch {
setFollowing(false);
}
setLoading(false);
};
const getFigures = async _account => {
setFiguresFetching(true);
try {
const {
data: { single, bundle, fav },
} = await getUserFigures(_account);
setCount(single);
setBundleCount(bundle);
setFavCount(fav);
} catch {
setCount(0);
setBundleCount(0);
setFavCount(0);
}
setFiguresFetching(false);
};
const fetchNFTs = async () => {
if (tab === 0) {
if (fetching) return;
setFetching(true);
} else {
if (bundleFetching) return;
setBundleFetching(true);
}
try {
const start = tab === 0 ? tokens.current.length : bundles.current.length;
const _count =
fetchCount -
((tab === 0 ? tokens.current : bundles.current).length % numPerRow);
const { data } = await fetchTokens(
start,
_count,
tab === 0 ? 'single' : 'bundle',
[],
null,
'createdAt',
[],
uid
);
if (tab === 0) {
// eslint-disable-next-line require-atomic-updates
tokens.current = [...tokens.current, ...data.tokens];
setCount(data.total);
if (authToken) {
updateItems(tokens.current)
.then(_tokens => (tokens.current = _tokens))
.catch();
}
} else {
// eslint-disable-next-line require-atomic-updates
bundles.current = [...bundles.current, ...data.tokens];
setBundleCount(data.total);
if (authToken) {
updateItems(bundles.current)
.then(_bundles => (bundles.current = _bundles))
.catch();
}
}
setFetching(false);
setBundleFetching(false);
} catch {
setFetching(false);
setBundleFetching(false);
}
};
const fetchLikes = async step => {
if (fetching) return;
setFavFetching(true);
try {
const { data } = await getMyLikes(step, uid);
setFavFetching(false);
likes.current = [...likes.current, ...data.tokens];
setFavCount(data.total);
if (authToken) {
updateItems(likes.current)
.then(_likes => (likes.current = _likes))
.catch();
}
setPage(step);
} catch {
setFavFetching(false);
}
};
useEffect(() => {
setPrevNumPerRow(numPerRow);
if (isNaN(numPerRow) || (prevNumPerRow && prevNumPerRow !== numPerRow))
return;
if (prevUID !== uid) {
setPrevUID(uid);
getUserDetails(uid);
getFigures(uid);
setTab(0);
if (tab === 0) {
init();
}
} else {
init();
}
}, [uid, tab, chainId, numPerRow]);
useEffect(() => {
if (me && user && me.address?.toLowerCase() === uid.toLowerCase()) {
setUser({ ...user, ...me });
}
if (account === undefined) {
setFollowing(false);
}
}, [me, uid, account]);
const updateCollections = async () => {
try {
dispatch(CollectionsActions.fetchStart());
const res = await fetchCollections();
if (res.status === 'success') {
const verified = [];
const unverified = [];
res.data.map(item => {
if (item.isVerified) verified.push(item);
else unverified.push(item);
});
dispatch(CollectionsActions.fetchSuccess([...verified, ...unverified]));
}
} catch {
dispatch(CollectionsActions.fetchFailed());
}
};
useEffect(() => {
if (fetchInterval) {
clearInterval(fetchInterval);
}
updateCollections();
setFetchInterval(setInterval(updateCollections, 1000 * 60 * 10));
}, [chainId]);
const isMe = account?.toLowerCase() === uid.toLowerCase();
useEffect(() => {
dispatch(HeaderActions.toggleSearchbar(true));
setInterval(() => setNow(new Date()), 1000);
}, []);
const updateItems = async _tokens => {
return new Promise((resolve, reject) => {
if (!authToken) {
return resolve(
_tokens.map(tk => ({
...tk,
isLiked: false,
}))
);
}
let missingTokens = _tokens.map((tk, index) =>
tk.items
? {
index,
isLiked: tk.isLiked,
bundleID: tk._id,
}
: {
index,
isLiked: tk.isLiked,
contractAddress: tk.contractAddress,
tokenID: tk.tokenID,
}
);
if (prevAuthToken) {
missingTokens = missingTokens.filter(tk => tk.isLiked === undefined);
}
if (missingTokens.length === 0) {
reject();
return;
}
const cancelTokenSource = axios.CancelToken.source();
setLikeCancelSource(cancelTokenSource);
getItemsLiked(missingTokens, authToken, cancelTokenSource.token)
.then(({ data, status }) => {
setLikeCancelSource(null);
if (status === 'success') {
const newTokens = [...tokens.current];
missingTokens.map((tk, idx) => {
newTokens[tk.index].isLiked = data[idx].isLiked;
});
resolve(newTokens);
}
})
.catch(() => {
reject();
});
});
};
useEffect(() => {
if (likeCancelSource) {
likeCancelSource.cancel();
}
if (tokens.current.length) {
updateItems(tokens.current)
.then(_tokens => (tokens.current = _tokens))
.catch();
}
if (bundles.current.length) {
updateItems(bundles.current)
.then(_bundles => (bundles.current = _bundles))
.catch();
}
if (likes.current.length) {
updateItems(likes.current)
.then(_likes => (likes.current = _likes))
.catch();
}
}, [authToken]);
const loadNextPage = () => {
if (fetching || bundleFetching) return;
if (tab === 0 && tokens.current.length === count) return;
if (tab === 1 && bundles.current.length === bundleCount) return;
if (tab === 2 && likes.current.length === favCount) return;
if (tab === 0 || tab === 1) {
fetchNFTs();
} else {
fetchLikes(page + 1);
}
};
const handleScroll = e => {
if (tab > 2) return;
const obj = e.currentTarget;
if (obj.scrollHeight - obj.clientHeight - obj.scrollTop < 100) {
loadNextPage();
}
};
const init = () => {
if (tab === 0) {
tokens.current = [];
setCount(0);
fetchNFTs();
} else if (tab === 1) {
bundles.current = [];
setBundleCount(0);
fetchNFTs();
} else if (tab === 2) {
likes.current = [];
setFavCount(0);
fetchLikes(0);
} else if (tab === 3) {
getActivity();
} else if (tab === 4) {
getOffersFromOthers();
} else if (tab === 5) {
getOffers();
}
};
const handleClose = () => {
setAnchorEl(null);
};
const handleCopyLink = () => {
handleClose();
toast('success', 'Link copied to clipboard!');
};
const handleShareOnFacebook = () => {
handleClose();
window.open(
`https://www.facebook.com/sharer/sharer.php?u=${window.location.href}`,
'_blank'
);
};
const handleShareToTwitter = () => {
handleClose();
window.open(
`https://twitter.com/intent/tweet?text=Check%20out%20this%20account%20on%20Artion&url=${window.location.href}`,
'_blank'
);
};
const goToTab = _tab => {
tokens.current = [];
bundles.current = [];
likes.current = [];
setTab(_tab);
};
const getActivity = async () => {
try {
setActivityLoading(true);
const { data } = await getAccountActivity(uid);
const _activities = [];
data.bids.map(bActivity => _activities.push(bActivity));
data.listings.map(lActivity => _activities.push(lActivity));
data.offers.map(oActivity => _activities.push(oActivity));
data.sold.map(sActivity => _activities.push(sActivity));
_activities.sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1));
_activities.map(item => {
item.token = getTokenByAddress(item.paymentToken);
});
setActivities(_activities);
setActivityLoading(false);
} catch {
setActivityLoading(false);
}
};
const getOffersFromOthers = async () => {
try {
setOffersLoading(true);
const { data } = await getActivityFromOthers(uid);
data.sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1));
data.map(item => {
item.token = getTokenByAddress(item.paymentToken);
});
setOffers(data);
setOffersLoading(false);
} catch {
setOffersLoading(false);
}
};
const getOffers = async () => {
try {
setBidsLoading(true);
const { data } = await getMyOffers(uid);
data.sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1));
data.map(item => {
item.token = getTokenByAddress(item.paymentToken);
});
setBids(data);
setBidsLoading(false);
} catch {
setBidsLoading(false);
}
};
const handleCopyAddress = () => {
setCopied(true);
setTimeout(() => {
setCopied(false);
}, 3000);
};
const handleMouseOver = () => {
setTooltipOpen(true);
};
const handleMouseLeave = () => {
setTooltipOpen(false);
setCopied(false);
};
const selectBanner = () => {
fileInput.current?.click();
};
const handleSelectFile = e => {
if (e.target.files.length > 0) {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = async function(e) {
const { data } = await updateBanner(e.target.result, authToken);
setBannerHash(data);
};
reader.readAsDataURL(file);
}
e.target.value = null;
};
const openAccountSettings = () => {
dispatch(ModalActions.showAccountModal());
};
// const handleCreateBundle = () => {
// setBundleModalVisible(true);
// };
const fetchFollowers = async () => {
setFollowersLoading(true);
try {
const { data } = await getFollowers(uid);
followers.current = data;
} catch {
followers.current = [];
}
setFollowersLoading(false);
};
const fetchFollowings = async () => {
setFollowersLoading(true);
try {
const { data } = await getFollowings(uid);
followings.current = data;
} catch {
followings.current = [];
}
setFollowersLoading(false);
};
const showFollowers = () => {
if (loading || !user.followers || user.followers === 0) return;
setFollowersModalVisible(true);
fetchFollowers();
};
const showFollowings = () => {
if (loading || !user.followings || user.followings === 0) return;
setFollowingsModalVisible(true);
fetchFollowings();
};
const followUser = async () => {
if (followingInProgress) return;
if (account === undefined) {
toast('error', 'Please connect your wallet!');
return;
}
setFollowingInProgress(true);
try {
const { status, data } = await _followUser(uid, !following, authToken);
if (status === 'success') {
const { data } = await getUserAccountDetails(uid);
setUser(data);
setFollowing(!following);
} else {
toast('error', data);
}
} catch (e) {
console.log(e);
}
setFollowingInProgress(false);
};
const formatDate = _date => {
const date = new Date(_date);
const diff = Math.floor((now - date.getTime()) / 1000);
if (diff >= ONE_MONTH) {
const m = Math.ceil(diff / ONE_MONTH);
return `${m} Month${m > 1 ? 's' : ''} Ago`;
}
if (diff >= ONE_DAY) {
const d = Math.ceil(diff / ONE_DAY);
return `${d} Day${d > 1 ? 's' : ''} Ago`;
}
if (diff >= ONE_HOUR) {
const h = Math.ceil(diff / ONE_HOUR);
return `${h} Hour${h > 1 ? 's' : ''} Ago`;
}
if (diff >= ONE_MIN) {
const h = Math.ceil(diff / ONE_MIN);
return `${h} Min${h > 1 ? 's' : ''} Ago`;
}
return `${diff} Second${diff > 1 ? 's' : ''} Ago`;
};
if (!isAddress(uid)) {
return <Redirect to="/404" />;
}
const renderMedia = image => {
if (image?.includes('youtube')) {
return (
<ReactPlayer
className={styles.mediaInner}
url={image}
controls={true}
width="100%"
height="100%"
/>
);
} else {
return (
<Suspense
fallback={
<Loader type="Oval" color="#007BFF" height={32} width={32} />
}
>
<SuspenseImg className={styles.mediaInner} src={image} />
</Suspense>
);
}
};
const renderTab = (label, Icon, idx, count, countLoading) => (
<div
className={cx(styles.tab, idx === tab && styles.selected)}
onClick={() => goToTab(idx)}
>
<Icon className={styles.tabIcon} />
<div className={styles.tabLabel}>{label}</div>
<div className={styles.tabCount}>
{countLoading ? (
<Skeleton className={styles.tabCountLoading} width={40} height={22} />
) : (
count
)}
</div>
</div>
);
return (
<div className={styles.container}>
<Header border />
<div className={styles.profile}>
<div className={styles.banner}>
{loading ? (
<Skeleton width="100%" height={200} />
) : bannerHash || user.bannerHash ? (
<img
src={`https://cloudflare-ipfs.com/ipfs/${bannerHash ||
user.bannerHash}`}
className={styles.bannerImg}
/>
) : (
<div className={styles.bannerPlaceholder} />
)}
{isMe && (
<div className={styles.editBanner} onClick={selectBanner}>
<input
ref={fileInput}
hidden
type="file"
onChange={handleSelectFile}
accept="image/*"
/>
<EditIcon className={styles.editIcon} />
</div>
)}
</div>
<div className={styles.buttonsWrapper}>
{isMe && (
<div className={styles.settings} onClick={openAccountSettings}>
<img src={iconSettings} className={styles.settingsIcon} />
</div>
)}
<div
className={styles.settings}
onClick={e => setAnchorEl(e.currentTarget)}
>
<img src={iconShare} className={styles.settingsIcon} />
</div>
</div>
<div className={styles.wrapper}>
<div className={styles.avatarWrapper}>
{loading ? (
<Skeleton width={160} height={160} className={styles.avatar} />
) : user.imageHash ? (
<img
src={`https://cloudflare-ipfs.com/ipfs/${user.imageHash}`}
className={styles.avatar}
/>
) : (
<Identicon className={styles.avatar} account={uid} size={160} />
)}
</div>
<div className={styles.usernameWrapper}>
{loading ? (
<Skeleton width={120} height={24} />
) : (
<div className={styles.username}>{user.alias || 'Unnamed'}</div>
)}
{isMe ? null : loading ? (
<Skeleton width={80} height={26} style={{ marginLeft: 16 }} />
) : (
<div
className={cx(
styles.followBtn,
followingInProgress && styles.disabled
)}
onClick={followUser}
>
{followingInProgress ? (
<ClipLoader color="#FFF" size={14} />
) : following ? (
'Unfollow'
) : (
'Follow'
)}
</div>
)}
</div>
<div className={styles.bio}>{user.bio || ''}</div>
<div className={styles.bottomWrapper}>
<div className={styles.addressWrapper}>
{loading ? (
<Skeleton width={120} height={20} />
) : (
<Tooltip
title={copied ? 'Copied!' : 'Copy'}
open={tooltipOpen}
arrow
classes={{ tooltip: styles.tooltip }}
>
<div className={styles.address}>{shortenAddress(uid)}</div>
</Tooltip>
)}
<CopyToClipboard text={uid} onCopy={handleCopyAddress}>
<div className={styles.copyIcon}>
<img
src={iconCopy}
onMouseOver={handleMouseOver}
onMouseLeave={handleMouseLeave}
/>
</div>
</CopyToClipboard>
</div>
<div className={styles.followers} onClick={showFollowers}>
{loading ? (
<Skeleton width={100} height={24} />
) : (
<>
<b>{formatFollowers(user.followers || 0)}</b> Followers
</>
)}
</div>
<div className={styles.followers} onClick={showFollowings}>
{loading ? (
<Skeleton width={100} height={24} />
) : (
<>
<b>{formatFollowers(user.followings || 0)}</b> Following
</>
)}
</div>
</div>
</div>
</div>
<div className={styles.content}>
<div className={styles.contentSidebar}>
<div className={styles.tabsGroup}>
<div className={styles.groupTitle}>My Items</div>
{renderTab(
'Single Items',
IconList,
0,
count,
fetching || fguresFetching
)}
{/* {renderTab(
'Bundles',
IconBundle,
1,
bundleCount,
bundleFetching || fguresFetching
)} */}
{renderTab(
'Favorited',
IconHeart,
2,
favCount,
favFetching || fguresFetching
)}
</div>
<div className={styles.tabsGroup}>
<div className={styles.groupTitle}>Account</div>
{renderTab('Activity', IconClock, 3)}
{renderTab('Offers', IconList, 4)}
{renderTab('My Offers', IconList, 5)}
</div>
</div>
<div ref={ref} className={styles.contentBody} onScroll={handleScroll}>
{tab === 0 ? (
<NFTsGrid
items={tokens.current}
numPerRow={numPerRow}
loading={fetching}
/>
) : // tab === 1 ? (
// <NFTsGrid
// items={bundles.current}
// numPerRow={numPerRow}
// loading={fetching}
// showCreate={isMe}
// onCreate={handleCreateBundle}
// />
// ) :
tab === 2 ? (
<NFTsGrid
items={likes.current}
numPerRow={numPerRow}
loading={fetching}
onLike={() => {
likes.current = [];
fetchLikes(0);
}}
/>
) : tab === 3 ? (
<div className={styles.tableWapper}>
<div className={styles.activityHeader}>
<div className={styles.event}>Event</div>
<div className={styles.name}>Item</div>
<div className={styles.price}>Price</div>
<div className={styles.quantity}>Quantity</div>
<div className={styles.owner}>Owner</div>
<div className={styles.date}>Date</div>
</div>
<div className={styles.activityList}>
{(activityLoading ? new Array(5).fill(null) : activities).map(
(activity, idx) => (
<div key={idx} className={styles.activity}>
<div className={styles.event}>
{activity ? (
activity.event
) : (
<Skeleton width={100} height={20} />
)}
</div>
{activity ? (
<Link
to={`/explore/${activity.contractAddress}/${activity.tokenID}`}
className={styles.name}
>
<div className={styles.media}>
{renderMedia(
activity.thumbnailPath.length > 10
? `${storageUrl}/image/${activity.thumbnailPath}`
: activity.imageURL
)}
</div>
{activity.name}
</Link>
) : (
<div className={styles.name}>
<Skeleton width={120} height={20} />
</div>
)}
<div className={styles.price}>
{activity ? (
<>
<div className={styles.tokenLogo}>
<img src={activity.token?.icon} />
</div>
{activity.price}
</>
) : (
<Skeleton width={100} height={20} />
)}
</div>
<div className={styles.quantity}>
{activity ? (
activity.quantity
) : (
<Skeleton width={80} height={20} />
)}
</div>
{activity ? (
activity.to ? (
<Link
to={`/account/${activity.to}`}
className={styles.owner}
>
<div className={styles.ownerAvatarWrapper}>
{activity.image ? (
<img
src={`https://cloudflare-ipfs.com/ipfs/${activity.image}`}
className={styles.ownerAvatar}
/>
) : (
<Identicon
account={activity.to}
size={24}
className={styles.ownerAvatar}
/>
)}
</div>
{activity.alias || shortenAddress(activity.to)}
</Link>
) : (
<div className={styles.owner} />
)
) : (
<div className={styles.owner}>
<Skeleton width={130} height={20} />
</div>
)}
<div className={styles.date}>
{activity ? (
formatDate(activity.createdAt)
) : (
<Skeleton width={120} height={20} />
)}
</div>
</div>
)
)}
</div>
</div>
) : tab === 4 ? (
<>
<div className={styles.activityHeader}>
<div className={styles.name}>Item</div>
<div className={styles.owner}>From</div>
<div className={styles.price}>Price</div>
<div className={styles.quantity}>Quantity</div>
<div className={styles.date}>Date</div>
</div>
<div className={styles.activityList}>
{(offersLoading
? new Array(5).fill(null)
: offers.filter(
offer => offer.deadline * 1000 > now.getTime()
)
).map((offer, idx) => (
<div key={idx} className={styles.activity}>
{offer ? (
<Link
to={`/explore/${offer.contractAddress}/${offer.tokenID}`}
className={styles.name}
>
<div className={styles.media}>
{renderMedia(
offer.thumbnailPath.length > 10
? `${storageUrl}/image/${offer.thumbnailPath}`
: offer.imageURL
)}
</div>
{offer.name}
</Link>
) : (
<div className={styles.name}>
<Skeleton width={120} height={20} />
</div>
)}
{offer ? (
<Link
to={`/account/${offer.creator}`}
className={styles.owner}
>
<div className={styles.ownerAvatarWrapper}>
{offer.image ? (
<img
src={`https://cloudflare-ipfs.com/ipfs/${offer.image}`}
className={styles.ownerAvatar}
/>
) : (
<Identicon
account={offer.creator}
size={24}
className={styles.ownerAvatar}
/>
)}
</div>
{offer.alias || shortenAddress(offer.creator)}
</Link>
) : (
<div className={styles.owner}>
<Skeleton width={130} height={20} />
</div>
)}
<div className={styles.price}>
{offer ? (
<>
<div className={styles.tokenLogo}>
<img src={offer.token?.icon} />
</div>
{offer.pricePerItem}
</>
) : (
<Skeleton width={100} height={20} />
)}
</div>
<div className={styles.quantity}>
{offer ? (
offer.quantity
) : (
<Skeleton width={80} height={20} />
)}
</div>
<div className={styles.date}>
{offer ? (
formatDate(offer.createdAt)
) : (
<Skeleton width={120} height={20} />
)}
</div>
</div>
))}
</div>
</>
) : (
<>
<div className={styles.activityHeader}>
<div className={styles.name}>Item</div>
<div className={styles.price}>Price</div>
<div className={styles.quantity}>Quantity</div>
<div className={styles.date}>Date</div>
</div>
<div className={styles.activityList}>
{(bidsLoading
? new Array(5).fill(null)
: bids.filter(bid => bid.deadline * 1000 > now.getTime())
).map((bid, idx) => (
<div key={idx} className={styles.activity}>
{bid ? (
<Link
to={`/explore/${bid.contractAddress}/${bid.tokenID}`}
className={styles.name}
>
<div className={styles.media}>
{renderMedia(
bid.thumbnailPath.length > 10
? `${storageUrl}/image/${bid.thumbnailPath}`
: bid.imageURL
)}
</div>
{bid.name}
</Link>
) : (
<div className={styles.name}>
<Skeleton width={120} height={20} />
</div>
)}
<div className={styles.price}>
{bid ? (
<>
<div className={styles.tokenLogo}>
<img src={bid.token?.icon} />
</div>
{bid.pricePerItem}
</>
) : (
<Skeleton width={100} height={20} />
)}
</div>
<div className={styles.quantity}>
{bid ? bid.quantity : <Skeleton width={80} height={20} />}
</div>
<div className={styles.date}>
{bid ? (
formatDate(bid.createdAt)
) : (
<Skeleton width={120} height={20} />
)}
</div>
</div>
))}
</div>
</>
)}
</div>
</div>
<Menu
id="simple-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
classes={{ paper: styles.shareMenu, list: styles.shareMenuList }}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'right',
}}
>
<CopyToClipboard text={window.location.href} onCopy={handleCopyLink}>
<MenuItem classes={{ root: styles.menuItem }}>
<img src={iconArtion} />
Copy Link
</MenuItem>
</CopyToClipboard>
<MenuItem
classes={{ root: styles.menuItem }}
onClick={handleShareOnFacebook}
>
<img src={iconFacebook} />
Share on Facebook
</MenuItem>
<MenuItem
classes={{ root: styles.menuItem }}
onClick={handleShareToTwitter}
>
<img src={iconTwitter} />
Share to Twitter
</MenuItem>
</Menu>
<NewBundleModal
visible={bundleModalVisible}
onClose={() => setBundleModalVisible(false)}
onCreateSuccess={() => {
bundles.current = [];
fetchNFTs();
}}
/>
<FollowersModal
visible={followersModalVisible || followingsModalVisible}
onClose={() => {
setFollowersModalVisible(false);
setFollowingsModalVisible(false);
}}
title={followersModalVisible ? 'Followers' : 'Followings'}
users={
followersLoading
? new Array(5).fill(null)
: followersModalVisible
? followers.current
: followings.current
}
/>
</div>
);
}
Example #2
Source File: index.js From Artion-Client with GNU General Public License v3.0 | 4 votes |
CollectionCreate = ({ isRegister }) => {
const dispatch = useDispatch();
const history = useHistory();
const { account } = useWeb3React();
const { apiUrl, getNonce } = useApi();
const {
getFactoryContract,
getPrivateFactoryContract,
getArtFactoryContract,
getPrivateArtFactoryContract,
createNFTContract,
} = useFactoryContract();
const inputRef = useRef(null);
const { authToken } = useSelector(state => state.ConnectWallet);
const [deploying, setDeploying] = useState(false);
const [creating, setCreating] = useState(false);
const [logo, setLogo] = useState(null);
const [anchorEl, setAnchorEl] = useState(null);
const [selected, setSelected] = useState([]);
const [name, setName] = useState('');
const [nameError, setNameError] = useState(null);
const [symbol, setSymbol] = useState('');
const [symbolError, setSymbolError] = useState(null);
const [description, setDescription] = useState('');
const [descriptionError, setDescriptionError] = useState(null);
const [royalty, setRoyalty] = useState('');
const [feeRecipient, setFeeRecipient] = useState('');
const [recipientError, setRecipientError] = useState(null);
const [email, setEmail] = useState('');
const [emailError, setEmailError] = useState(null);
const [address, setAddress] = useState('');
const [addressError, setAddressError] = useState('');
const [siteUrl, setSiteUrl] = useState('');
const [discord, setDiscord] = useState('');
const [twitterHandle, setTwitterHandle] = useState('');
const [instagramHandle, setInstagramHandle] = useState('');
const [mediumHandle, setMediumHandle] = useState('');
const [telegram, setTelegram] = useState('');
const [isPrivate, setIsPrivate] = useState(false);
const [isSingle, setIsSingle] = useState(true);
const isMenuOpen = Boolean(anchorEl);
useEffect(() => {
dispatch(HeaderActions.toggleSearchbar(true));
}, []);
useEffect(() => {
setLogo(null);
setAnchorEl(null);
setSelected([]);
setName('');
setNameError(null);
setSymbol('');
setSymbolError(null);
setDescription('');
setDescriptionError(null);
setEmail('');
setEmailError(null);
setAddress('');
setAddressError(null);
setSiteUrl('');
setDiscord('');
setTwitterHandle('');
setInstagramHandle('');
setMediumHandle('');
setTelegram('');
}, [isRegister]);
const options = Categories.filter(cat => selected.indexOf(cat.id) === -1);
const selectedCategories = Categories.filter(
cat => selected.indexOf(cat.id) > -1
);
const removeImage = () => {
setLogo(null);
};
const handleFileSelect = e => {
if (e.target.files.length > 0) {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = function(e) {
setLogo(e.target.result);
};
reader.readAsDataURL(file);
}
};
const validateName = () => {
if (name.length === 0) {
setNameError("This field can't be blank");
} else {
setNameError(null);
}
};
const validateSymbol = () => {
if (symbol.length === 0) {
setSymbolError("This field can't be blank");
} else if (symbol.includes(' ')) {
setSymbolError("Symbol can't include spaces");
} else {
setSymbolError(null);
}
};
const validateDescription = () => {
if (description.length === 0) {
setDescriptionError("This field can't be blank");
} else {
setDescriptionError(null);
}
};
const validateFeeRecipient = () => {
if (feeRecipient.length === 0) {
setRecipientError("This field can't be blank");
} else if (!isAddress(feeRecipient)) {
setRecipientError('Invalid address');
} else {
setRecipientError(null);
}
};
const validEmail = email => /(.+)@(.+){2,}\.(.+){2,}/.test(email);
const validateEmail = () => {
if (email.length === 0) {
setEmailError("This field can't be blank");
} else if (validEmail(email)) {
setEmailError(null);
} else {
setEmailError('Invalid email address.');
}
};
const validateAddress = () => {
if (address.length === 0) {
setAddressError("This field can't be blank");
} else {
setAddressError(null);
}
};
const handleMenuOpen = e => {
if (selected.length < 3) {
setAnchorEl(e.currentTarget);
}
};
const handleMenuClose = () => {
setAnchorEl(null);
};
const selectCategory = catId => {
setSelected([...selected, catId]);
if (selected.length === 2) {
setAnchorEl(null);
}
};
const deselectCategory = catId => {
setSelected(selected.filter(id => id !== catId));
};
const isValid = (() => {
if (!logo) return false;
if (nameError) return false;
if (descriptionError) return false;
if (addressError) return false;
if (!isRegister && (symbol.length === 0 || symbol.includes(' ')))
return false;
if (email.length === 0) return false;
if (!validEmail(email)) return false;
if (isRegister && !isAddress(feeRecipient)) return false;
return true;
})();
const clipImage = (image, clipX, clipY, clipWidth, clipHeight, cb) => {
const CANVAS_SIZE = 128;
const canvas = document.createElement('canvas');
canvas.width = CANVAS_SIZE;
canvas.height = CANVAS_SIZE;
const ctx = canvas.getContext('2d');
ctx.drawImage(
image,
clipX,
clipY,
clipWidth,
clipHeight,
0,
0,
CANVAS_SIZE,
CANVAS_SIZE
);
cb(canvas.toDataURL());
};
const handleRegister = async () => {
if (creating) return;
setCreating(true);
const img = new Image();
img.onload = function() {
const w = this.width;
const h = this.height;
const size = Math.min(w, h);
const x = (w - size) / 2;
const y = (h - size) / 2;
clipImage(img, x, y, size, size, async logodata => {
try {
const { data: nonce } = await getNonce(account, authToken);
let signature;
let signatureAddress;
try {
const signer = await getSigner();
const msg = `Approve Signature on Artion.io with nonce ${nonce}`;
signature = await signer.signMessage(msg);
signatureAddress = ethers.utils.verifyMessage(msg, signature);
} catch (err) {
toast(
'error',
'You need to sign the message to be able to register a collection.'
);
setCreating(false);
return;
}
const formData = new FormData();
formData.append('collectionName', name);
formData.append('erc721Address', address);
formData.append('imgData', logodata);
const result = await axios({
method: 'post',
url: `${apiUrl}/ipfs/uploadCollectionImage2Server`,
data: formData,
headers: {
'Content-Type': 'multipart/form-data',
Authorization: `Bearer ${authToken}`,
},
});
const logoImageHash = result.data.data;
const data = {
email,
erc721Address: address,
collectionName: name,
description,
categories: selected.join(','),
logoImageHash,
siteUrl,
discord,
twitterHandle,
instagramHandle,
mediumHandle,
telegram,
signature,
signatureAddress,
royalty,
feeRecipient,
};
await axios({
method: 'post',
url: `${apiUrl}/collection/collectiondetails`,
data: JSON.stringify(data),
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${authToken}`,
},
});
toast(
'success',
'Application submitted!',
'Your collection registration application is successfully submitted for review.\nOnce approved, you will get an email notification.'
);
setCreating(false);
history.push('/explore');
} catch (e) {
console.log('Error: ', e);
setCreating(false);
}
});
};
img.src = logo;
};
const handleCreate = async () => {
setDeploying(true);
try {
const tx = await createNFTContract(
isSingle
? isPrivate
? await getPrivateFactoryContract()
: await getFactoryContract()
: isPrivate
? await getPrivateArtFactoryContract()
: await getArtFactoryContract(),
name,
symbol,
ethers.utils.parseEther('100'),
account
);
const res = await tx.wait();
res.events.map(evt => {
if (
evt.topics[0] ===
'0x2d49c67975aadd2d389580b368cfff5b49965b0bd5da33c144922ce01e7a4d7b'
) {
setDeploying(false);
setCreating(true);
const address = ethers.utils.hexDataSlice(evt.data, 44);
const img = new Image();
img.onload = function() {
const w = this.width;
const h = this.height;
const size = Math.min(w, h);
const x = (w - size) / 2;
const y = (h - size) / 2;
clipImage(img, x, y, size, size, async logodata => {
try {
const { data: nonce } = await getNonce(account, authToken);
let signature;
try {
const signer = await getSigner();
signature = await signer.signMessage(
`Approve Signature on Artion.io with nonce ${nonce}`
);
} catch (err) {
toast(
'error',
'You need to sign the message to be able to create a collection.'
);
setCreating(false);
return;
}
const formData = new FormData();
formData.append('collectionName', name);
formData.append('erc721Address', address);
formData.append('imgData', logodata);
const result = await axios({
method: 'post',
url: `${apiUrl}/ipfs/uploadCollectionImage2Server`,
data: formData,
headers: {
'Content-Type': 'multipart/form-data',
Authorization: `Bearer ${authToken}`,
},
});
const logoImageHash = result.data.data;
const data = {
email,
erc721Address: address,
collectionName: name,
description,
categories: selected.join(','),
logoImageHash,
siteUrl,
discord,
twitterHandle,
instagramHandle,
mediumHandle,
telegram,
signature,
};
await axios({
method: 'post',
url: `${apiUrl}/collection/collectiondetails`,
data: JSON.stringify(data),
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${authToken}`,
},
});
toast('success', 'Collection created successfully!');
setCreating(false);
history.push('/explore');
} catch (e) {
setCreating(false);
}
});
};
img.src = logo;
}
});
} catch (err) {
showToast('error', formatError(err));
console.log(err);
setDeploying(false);
}
};
const menuId = 'select-category-menu';
const renderMenu = (
<Menu
anchorEl={anchorEl}
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
id={menuId}
keepMounted
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
open={isMenuOpen}
onClose={handleMenuClose}
classes={{
paper: styles.menu,
}}
>
{options.map((cat, idx) => (
<MenuItem
key={idx}
className={styles.category}
onClick={() => selectCategory(cat.id)}
>
<img src={cat.icon} />
<span className={styles.categoryLabel}>{cat.label}</span>
</MenuItem>
))}
</Menu>
);
return (
<div className={styles.container}>
<Header border />
<div className={styles.inner}>
<div className={styles.title}>
{isRegister ? 'Register' : 'Create New'} Collection
</div>
<br />
<div style={{ fontSize: '13px' }}>
Please submit using the owner address of the collection. If you cannot
use the owner address, please email us on [email protected]
with the information below (and proof of collection ownership, such as
from the collection's official email address).
</div>
{!isRegister && (
<div className={styles.inputGroup}>
<RadioGroup
className={styles.inputWrapper}
value={JSON.stringify(isPrivate)}
onChange={e => setIsPrivate(e.currentTarget.value === 'true')}
>
<FormControlLabel
classes={{
root: cx(styles.option, !isPrivate && styles.active),
label: styles.optionLabel,
}}
value="false"
control={<CustomRadio color="primary" />}
label="Allow others mint NFTs under my collection"
/>
<FormControlLabel
classes={{
root: cx(styles.option, isPrivate && styles.active),
label: styles.optionLabel,
}}
value="true"
control={<CustomRadio color="primary" />}
label="Only I can mint NFTs under my collection"
/>
</RadioGroup>
</div>
)}
<div className={styles.inputGroup}>
<div className={styles.inputTitle}>Logo Image *</div>
<div className={styles.inputSubTitle}>
This image will also be used for navigation. 300x300 recommended.
</div>
<div className={styles.inputWrapper}>
<div className={styles.logoUploadBox}>
{logo ? (
<>
<img src={logo} />
<div className={styles.removeOverlay}>
<div className={styles.removeIcon} onClick={removeImage}>
<img src={closeIcon} />
</div>
</div>
</>
) : (
<div
className={styles.uploadOverlay}
onClick={() => inputRef.current?.click()}
>
<input
ref={inputRef}
type="file"
accept="image/*"
hidden
onChange={handleFileSelect}
/>
<div className={styles.upload}>
<div className={styles.uploadInner}>
<img src={uploadIcon} />
</div>
<div className={styles.plusIcon}>
<img src={plusIcon} />
</div>
</div>
</div>
)}
</div>
</div>
</div>
<div className={styles.inputGroup}>
<div className={styles.inputTitle}>Name *</div>
<div className={styles.inputWrapper}>
<input
className={cx(styles.input, nameError && styles.hasError)}
maxLength={20}
placeholder="Collection Name"
value={name}
onChange={e => setName(e.target.value)}
onBlur={validateName}
/>
<div className={styles.lengthIndicator}>{name.length}/20</div>
{nameError && <div className={styles.error}>{nameError}</div>}
</div>
</div>
{!isRegister && (
<div className={styles.inputGroup}>
<div className={styles.inputTitle}>
Symbol *
<BootstrapTooltip
title="A symbol is used when we deploy your NFT contract. If you are not sure about symbol, be aware that name and symbol share the same value."
placement="top"
>
<HelpOutlineIcon />
</BootstrapTooltip>
</div>
<div className={styles.inputWrapper}>
<input
className={cx(styles.input, symbolError && styles.hasError)}
maxLength={20}
placeholder="Collection Symbol"
value={symbol}
onChange={e => setSymbol(e.target.value)}
onBlur={validateSymbol}
/>
<div className={styles.lengthIndicator}>{symbol.length}/20</div>
{symbolError && <div className={styles.error}>{symbolError}</div>}
</div>
</div>
)}
<div className={styles.inputGroup}>
<div className={styles.inputTitle}>Description *</div>
<div className={styles.inputWrapper}>
<textarea
className={cx(
styles.input,
styles.longInput,
descriptionError && styles.hasError
)}
maxLength={200}
placeholder="Provide your description for your collection"
value={description}
onChange={e => setDescription(e.target.value)}
onBlur={validateDescription}
/>
<div className={styles.lengthIndicator}>
{description.length}/200
</div>
{descriptionError && (
<div className={styles.error}>{descriptionError}</div>
)}
</div>
</div>
{isRegister && (
<div className={styles.inputGroup}>
<div className={styles.inputTitle}>
Royalty *
<BootstrapTooltip
title="Each NFT under this collection exchanged through Artion will have a percentage of sale given to nominated wallet address."
placement="top"
>
<HelpOutlineIcon />
</BootstrapTooltip>
</div>
<div className={styles.inputWrapper}>
<PriceInput
className={styles.input}
placeholder="Collection Royalty"
decimals={2}
value={'' + royalty}
onChange={val =>
val[val.length - 1] === '.'
? setRoyalty(val)
: setRoyalty(Math.min(100, +val))
}
/>
</div>
</div>
)}
{isRegister && (
<div className={styles.inputGroup}>
<div className={styles.inputTitle}>
Fee Recipient *
<BootstrapTooltip
title="The nominated Fantom Opera Network wallet address to receive royalties from each sale in this collection."
placement="top"
>
<HelpOutlineIcon />
</BootstrapTooltip>
</div>
<div className={styles.inputWrapper}>
<input
className={cx(styles.input, recipientError && styles.hasError)}
placeholder="Fee Recipient"
value={feeRecipient}
onChange={e => setFeeRecipient(e.target.value)}
onBlur={validateFeeRecipient}
/>
{recipientError && (
<div className={styles.error}>{recipientError}</div>
)}
</div>
</div>
)}
{!isRegister && (
<div className={styles.inputGroup}>
<RadioGroup
className={styles.inputWrapper}
value={JSON.stringify(isSingle)}
onChange={e => setIsSingle(e.currentTarget.value === 'true')}
>
<FormControlLabel
classes={{
root: cx(styles.option, isSingle && styles.active),
label: styles.optionLabel,
}}
value="true"
control={<CustomRadio color="primary" />}
label="Single Token Standard"
/>
<FormControlLabel
classes={{
root: cx(styles.option, !isSingle && styles.active),
label: styles.optionLabel,
}}
value="false"
control={<CustomRadio color="primary" />}
label="Multi Token Standard"
/>
</RadioGroup>
</div>
)}
<div className={styles.inputGroup}>
<div className={styles.inputTitle}>Category</div>
<div className={styles.inputSubTitle}>
Adding a category will help make your item discoverable on Fantom.
</div>
<div className={styles.inputSubTitle}>
For more information, read{' '}
<a
href="https://docs.fantom.foundation/tutorials/collection-and-bundle-guide-on-artion"
target="_blank"
rel="noopener noreferrer"
>
this
</a>
</div>
<div className={cx(styles.inputWrapper, styles.categoryList)}>
<div
className={cx(
styles.categoryButton,
selected.length === 3 && styles.disabled
)}
onClick={handleMenuOpen}
>
Add Category
</div>
{selectedCategories.map((cat, idx) => (
<div
className={styles.selectedCategory}
key={idx}
onClick={() => deselectCategory(cat.id)}
>
<img src={cat.icon} className={styles.categoryIcon} />
<span className={styles.categoryLabel}>{cat.label}</span>
<CloseIcon className={styles.closeIcon} />
</div>
))}
</div>
</div>
<div className={styles.inputGroup}>
<div className={styles.inputTitle}>Links *</div>
<div className={styles.inputWrapper}>
<div className={styles.linksWrapper}>
{isRegister && (
<>
<div
className={cx(
styles.linkItem,
addressError && styles.hasError
)}
>
<div className={styles.linkIconWrapper}>
<img src={nftIcon} className={styles.linkIcon} />
</div>
<input
className={styles.linkInput}
placeholder="Enter your collection's address"
value={address}
onChange={e => setAddress(e.target.value)}
onBlur={validateAddress}
/>
</div>
{addressError && (
<div className={styles.error}>{addressError}</div>
)}
</>
)}
<div className={styles.linkItem}>
<div className={styles.linkIconWrapper}>
<img src={webIcon} className={styles.linkIcon} />
</div>
<input
className={styles.linkInput}
placeholder="Enter your website url"
value={siteUrl}
onChange={e => setSiteUrl(e.target.value)}
/>
</div>
<div className={styles.linkItem}>
<div className={styles.linkIconWrapper}>
<img src={discordIcon} className={styles.linkIcon} />
</div>
<input
className={styles.linkInput}
placeholder="Enter your Discord url"
value={discord}
onChange={e => setDiscord(e.target.value)}
/>
</div>
<div className={styles.linkItem}>
<div className={styles.linkIconWrapper}>
<img src={twitterIcon} className={styles.linkIcon} />
</div>
<input
className={styles.linkInput}
placeholder="Enter your Twitter profile link"
value={twitterHandle}
onChange={e => setTwitterHandle(e.target.value)}
/>
</div>
<div className={styles.linkItem}>
<div className={styles.linkIconWrapper}>
<img src={instagramIcon} className={styles.linkIcon} />
</div>
<input
className={styles.linkInput}
placeholder="Enter your Instagram profile link"
value={instagramHandle}
onChange={e => setInstagramHandle(e.target.value)}
/>
</div>
<div className={styles.linkItem}>
<div className={styles.linkIconWrapper}>
<img src={mediumIcon} className={styles.linkIcon} />
</div>
<input
className={styles.linkInput}
placeholder="Enter your Medium profile link"
value={mediumHandle}
onChange={e => setMediumHandle(e.target.value)}
/>
</div>
<div className={styles.linkItem}>
<div className={styles.linkIconWrapper}>
<img src={telegramIcon} className={styles.linkIcon} />
</div>
<input
className={styles.linkInput}
placeholder="Enter your Telegram profile link"
value={telegram}
onChange={e => setTelegram(e.target.value)}
/>
</div>
</div>
</div>
</div>
<div className={styles.inputGroup}>
<div className={styles.inputTitle}>
Contact Email *
<BootstrapTooltip
title="We will use this email to notify you about your collection application. This will not be shared with others."
placement="top"
>
<HelpOutlineIcon />
</BootstrapTooltip>
</div>
<div className={styles.inputWrapper}>
<input
className={cx(styles.input, emailError && styles.hasError)}
placeholder="Email Address"
value={email}
onChange={e => setEmail(e.target.value)}
onBlur={validateEmail}
/>
{emailError && <div className={styles.error}>{emailError}</div>}
</div>
</div>
<div className={styles.buttonsWrapper}>
{isRegister ? (
<div
className={cx(
styles.createButton,
(creating || !isValid) && styles.disabled
)}
onClick={isValid ? handleRegister : null}
>
{creating ? <ClipLoader color="#FFF" size={16} /> : 'Submit'}
</div>
) : (
<div
className={cx(
styles.createButton,
(creating || deploying || !isValid) && styles.disabled
)}
onClick={isValid && !creating && !deploying ? handleCreate : null}
>
{creating ? (
<ClipLoader color="#FFF" size={16} />
) : deploying ? (
'Deploying'
) : (
'Create'
)}
</div>
)}
</div>
{!isRegister && (
<div className={styles.fee}>
<InfoIcon />
100 FTMs are charged to create a new collection.
</div>
)}
</div>
{renderMenu}
</div>
);
}