Skip to content

Commit

Permalink
Merge pull request #20 from ElvenTools/profile
Browse files Browse the repository at this point in the history
Profile page
  • Loading branch information
juliancwirko authored Nov 27, 2022
2 parents 08fb068 + a02a52c commit dd42979
Show file tree
Hide file tree
Showing 21 changed files with 3,315 additions and 2,486 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
### [3.3.0](https://github.com/ElvenTools/elven-tools-dapp/releases/tag/v3.3.0) (2022-11-26)
- added Profile page with the list of minted NFTs and basic account/wallet information
- updated dependencies, including Next 13 (without changing the files structure yet)
- general API call hook added, for different API calls, not only blockchain-related. See more in the docs
- added `ProtectedPageWrapper` - client-side only page content wrapper component that will redirect to the chosen path when the user is not logged in. Wrap page content with it. Check the profile page, for example.

### [3.2.0](https://github.com/ElvenTools/elven-tools-dapp/releases/tag/v3.2.0) (2022-10-04)
- update dependencies
- show avatar from maiar mobile app if set
Expand Down
4 changes: 2 additions & 2 deletions components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const Footer = () => {
color="elvenTools.color3.base"
href="https://www.elven.tools"
target="_blank"
rel="noopener noreferrer nofollow"
rel="noopener noreferrer"
>
elven.tools
</Text>{' '}
Expand All @@ -38,7 +38,7 @@ export const Footer = () => {
color="elvenTools.color3.base"
href="https://www.julian.io"
target="_blank"
rel="noopener noreferrer nofollow"
rel="noopener noreferrer"
>
julian.io
</Text>
Expand Down
4 changes: 2 additions & 2 deletions components/HeaderMenuButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useCallback, FC } from 'react';
import { ActionButton } from './ActionButton';
import { SocialMediaIcons } from './SocialMediaIcons';
import { LoginModalButton } from './core/LoginModalButton';
import { UserMenu } from './UserMenu';
import { UserAvatar } from './UserAvatar';

interface HeaderMenuButtonsProps {
enabled: string[];
Expand Down Expand Up @@ -46,7 +46,7 @@ export const HeaderMenuButtons: FC<HeaderMenuButtonsProps> = ({ enabled }) => {

<SocialMediaIcons />

<UserMenu />
<UserAvatar />

{enabled.includes('mint') && (
<ActionButton onClick={handleMintClick}>Mint</ActionButton>
Expand Down
4 changes: 2 additions & 2 deletions components/Hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const Hero = () => {
color="elvenTools.color3.base"
href="https://www.elven.tools"
target="_blank"
rel="noopener noreferrer nofollow"
rel="noopener noreferrer"
>
Elven Tools
</Text>{' '}
Expand All @@ -55,7 +55,7 @@ export const Hero = () => {
color="elvenTools.color2.base"
href="https://www.elrond.com"
target="_blank"
rel="noopener noreferrer nofollow"
rel="noopener noreferrer"
>
Elrond
</Text>{' '}
Expand Down
14 changes: 6 additions & 8 deletions components/MintHero.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { Box, Text, useBreakpointValue } from '@chakra-ui/react';
import Link from 'next/link';
import { useCallback, useEffect } from 'react';
import { Address } from '@elrondnetwork/erdjs';
import { SCQueryType } from '../hooks/interaction/useScQuery';
Expand All @@ -8,7 +9,6 @@ import { MintForm } from './MintForm';
import { Authenticated } from './core/Authenticated';
import { useAccount } from '../hooks/auth/useAccount';
import { LoginModalButton } from './core/LoginModalButton';
import { networkConfig, chainType } from '../config/network';
import { NFTLeftToMint } from './NFTLeftToMint';
import { NFTAllowlistEnabled } from './NFTAllowlistEnabled';
import { NFTMintedAlready } from './NFTMintedAlready';
Expand Down Expand Up @@ -268,19 +268,17 @@ export const MintHero = () => {
>
Check your NFTs:
</Text>
<Text
as="a"
<Box
ml={3}
target="_blank"
color="elvenTools.color2.base"
fontSize="2xl"
fontWeight="black"
textDecoration="underline"
rel="noopener noreferrer nofollow"
href={`${networkConfig[chainType].explorerAddress}/accounts/${address}/nfts`}
>
here
</Text>
<Link color="elvenTools.color2.base" href="/profile">
here
</Link>
</Box>
</Box>
) : null}
</Authenticated>
Expand Down
98 changes: 98 additions & 0 deletions components/NftImageHelper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// After minting an NFT, there is no guarantee that the image thumbnail
// will be immediately available through Elrond API
// This is why we have this helper. It will fallback to the IPFS source using a custom
// IPFS gateway, we take the CID from Elrond's IPFS gateway url here to be sure that we will get it

import { FC, CSSProperties, PropsWithChildren } from 'react';
import { Box } from '@chakra-ui/react';
import Image, { ImageProps } from 'next/image';
import { customIPFSGateway, elrondIPFSGateway } from '../config/network';

const commonImageStyles: CSSProperties = {
objectFit: 'contain',
borderRadius: 10,
};

const commonImagesProps = {
sizes: '280px',
height: 280,
width: 280,
priority: true,
style: commonImageStyles,
placeholder: 'blur' as ImageProps['placeholder'],
blurDataURL:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARgAAAEYCAQAAAAthyEHAAAB0ElEQVR42u3SQQ0AAAgEIC+eqaxuBb9ukIH0FJxFGIRBGIRBGIQBYRAGYRAGYRAGhEEYhEEYhEEYEAZhEAZhEAaEQRiEQRiEQRgQBmEQBmEQBmFAGIRBGIRBGIQBYRAGYRAGYUAYhEEYhEEYhAFhEAZhEAZhEAaEQRiEQRiEQRgQBmEQBmEQBoRBGIRBGIRBGBAGYRAGYRAGYUAYhEEYhEEYhBEGYRAGYRAGYUAYhEEYhEEYhAFhEAZhEAZhEAaEQRiEQRiEAWEQBmEQBmEQBoRBGIRBGIRBGBAGYRAGYRAGYUAYhEEYhEEYEAZhEAZhEAZhQBiEQRiEQRiEAWEQBmEQBmEQBoRBGIRBGIQBYRAGYRAGYRAGhEEYhEEYhEEYEAZhEAZhEAZhhEEYhEEYhEEYEAZhEAZhEAZhQBiEQRiEQRiEAWEQBmEQBmFAGIRBGIRBGIQBYRAGYRAGYRAGhEEYhEEYhEEYEAZhEAZhEAaEQRiEQRiEQRgQBmEQBmEQBmFAGIRBGIRBGIQBYRAGYRAGYUAYhEEYhEEYhAFhEAZhEAZhEAaEQRiEQRiEQRhhEAZhEAZhEAaEQRiEQRiEQRgQBmEQBmEQBmFAGIRBGP5YwUTb2T0xhhoAAAAASUVORK5CYII=',
};

interface NftImageHelperProps {
thumbnail: string;
elrondIPFSGatewayUrl: string;
href?: string;
}

const isDefaultThumbnail = (thumbnail: string) => {
return thumbnail.includes('default.png');
};

const getImageUrlFromIPFS = (
elrondIPFSGatewayUrl: string,
thumbnail: string
) => {
if (elrondIPFSGatewayUrl) {
const CIDandImageFileName = elrondIPFSGatewayUrl.replace(
elrondIPFSGateway,
''
);
return `${customIPFSGateway}${CIDandImageFileName}`;
}
return thumbnail;
};

interface WithHrefProps {
href?: string;
}

const MaybeWithHref: FC<PropsWithChildren<WithHrefProps>> = ({
href,
children,
}) => {
if (href) {
return (
<Box
as="a"
href={href}
target="_blank"
rel="noopener noreferrer"
position="relative"
display="block"
>
{children}
</Box>
);
}
return <Box position="relative">{children}</Box>;
};

export const NftImageHelper: FC<NftImageHelperProps> = ({
thumbnail,
elrondIPFSGatewayUrl,
href,
}) => {
return (
<>
{isDefaultThumbnail(thumbnail) ? (
<MaybeWithHref href={href}>
<Image
src={getImageUrlFromIPFS(elrondIPFSGatewayUrl, thumbnail)}
alt=""
{...commonImagesProps}
/>
</MaybeWithHref>
) : (
<MaybeWithHref href={href}>
<Image src={thumbnail} alt="" {...commonImagesProps} />
</MaybeWithHref>
)}
</>
);
};
106 changes: 106 additions & 0 deletions components/ProfileNFTsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { Box, Stack, Spinner, Card, CardBody, Text } from '@chakra-ui/react';
import Link from 'next/link';
import { useAccount } from '../hooks/auth/useAccount';
import { useApiCall } from '../hooks/interaction/useApiCall';
import { NFT } from '../types/nfts';
import { SCQueryType } from '../hooks/interaction/useScQuery';
import { useElvenScQuery } from '../hooks/interaction/elvenScHooks/useElvenScQuery';
import { NftImageHelper } from './NftImageHelper';
import { networkConfig, chainType } from '../config/network';

const SIZE_PER_PAGE = 10000;

export const ProfileNFTsList = () => {
const { address } = useAccount();

const { data: collectionTicker, isLoading: collectionTickerLoading } =
useElvenScQuery<number>({
funcName: 'getNftTokenId',
type: SCQueryType.STRING,
});

const { data: nfts, isLoading: nftsDataPending } = useApiCall<NFT[]>({
url: `/accounts/${address}/nfts?collections=${collectionTicker}&size=${SIZE_PER_PAGE}`,
autoInit: Boolean(address) && Boolean(collectionTicker),
});

if (nftsDataPending || collectionTickerLoading) {
return (
<Stack
flex={1}
direction="row"
alignItems="center"
justifyContent="center"
mt={8}
>
<Spinner size="lg" />
</Stack>
);
}

if (!nfts || nfts.length === 0) {
return (
<Box mt={12} textAlign="center">
<Text>No NFTs minted yet!</Text>
<Link href="/mint">
<Text textDecoration="underline">Mint some!</Text>
</Link>
</Box>
);
}

return (
<>
<Stack
direction="row"
mt={12}
justifyContent="center"
flexWrap="wrap"
gap={6}
spacing={0}
>
{nfts?.map((nft) => (
<Card
maxW="xs"
minW="xs"
key={nft.identifier}
backgroundColor="elvenTools.dark.darker"
>
<CardBody>
<Stack height={280} position="relative">
<NftImageHelper
thumbnail={nft.media?.[0].thumbnailUrl}
elrondIPFSGatewayUrl={nft.url}
href={`${networkConfig[chainType].explorerAddress}/nfts/${nft.identifier}`}
/>
</Stack>
<Box
fontWeight={800}
mt={5}
color="elvenTools.white"
textAlign="center"
>
<a
href={`${networkConfig[chainType].explorerAddress}/nfts/${nft.identifier}`}
rel="noopener noreferrer"
target="_blank"
>
{nft.name}
</a>
</Box>
<Box color="elvenTools.white" textAlign="center">
<a
href={`${networkConfig[chainType].explorerAddress}/nfts/${nft.identifier}`}
rel="noopener noreferrer"
target="_blank"
>
{nft.identifier}
</a>
</Box>
</CardBody>
</Card>
))}
</Stack>
</>
);
};
87 changes: 87 additions & 0 deletions components/ProfileUserData.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Box, Avatar, Stack, Icon, Tooltip } from '@chakra-ui/react';
import { BiLink } from 'react-icons/bi';
import { avatarIdUrl, networkConfig, chainType } from '../config/network';
import { shortenHash } from '../utils/shortenHash';
import { Account } from '../types/account';
import { useApiCall } from '../hooks/interaction/useApiCall';
import { useAccount } from '../hooks/auth/useAccount';

export const ProfileUserData = () => {
const { address } = useAccount();

const { data: accountData, isLoading: accountDataPending } =
useApiCall<Account>({ url: `/accounts/${address}` });

return (
<Stack direction="row" justifyContent="center">
<Stack
direction="column"
alignItems="center"
justifyContent="center"
spacing={0}
>
<Avatar mt={8} size="2xl" src={avatarIdUrl(address)} />
<Box>
<Box mt={4} fontWeight={900} fontSize={32} display="inline-block">
{accountData?.username ? (
<Box>@{accountData?.username}</Box>
) : (
<Tooltip
bg="elvenTools.dark.darker"
fontWeight="light"
placement="top"
py={3}
px={5}
hasArrow
arrowSize={12}
borderRadius={10}
label={
'Check Buildo Begins tool on how to get one! (github.com/xdevguild/buildo-begins)'
}
>
<Box>
<a
href="https://github.com/xdevguild/buildo-begins#general-operations"
rel="noopener noreferrer"
target="_blank"
>
{accountDataPending ? '' : 'No herotag!'}
</a>
</Box>
</Tooltip>
)}
</Box>
</Box>
<Box>
<Box display="inline-block">
<Stack
direction="row"
alignItems="center"
as="a"
href={`${networkConfig[chainType].explorerAddress}/address/${address}`}
target="_blank"
rel="noopener noreferrer"
>
<Tooltip
bg="elvenTools.dark.darker"
fontWeight="light"
placement="top"
py={3}
px={5}
hasArrow
arrowSize={12}
borderRadius={10}
label={address}
>
<Box fontWeight={700} fontSize={22}>
{shortenHash(address, 8)}
</Box>
</Tooltip>
<Icon as={BiLink} w={6} h={6} />
</Stack>
</Box>
</Box>
</Stack>
</Stack>
);
};
Loading

0 comments on commit dd42979

Please sign in to comment.