Skip to content

Commit

Permalink
feat: profile page lists NFTs by wallet
Browse files Browse the repository at this point in the history
  • Loading branch information
netpoe committed May 13, 2024
1 parent 7a97f8d commit 82ee50b
Show file tree
Hide file tree
Showing 30 changed files with 1,082 additions and 446 deletions.
1 change: 1 addition & 0 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"lodash": "^4.17.21",
"materialize-css": "^1.0.0-rc.2",
"moment": "^2.29.1",
"moralis": "^2.26.1",
"next": "^14.2.1",
"next-i18next": "^8.2.0",
"react": "^18.2.0",
Expand Down
5 changes: 5 additions & 0 deletions app/src/context/evm/ERC721/Erc721Context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { createContext } from "react";

import { Erc721ContextType } from "./Erc721Context.types";

export const Erc721Context = createContext<Erc721ContextType | undefined>(undefined);
11 changes: 11 additions & 0 deletions app/src/context/evm/ERC721/Erc721Context.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { GetNFTsByWalletResult } from "api/evm/ERC721/types";
import { ReactNode } from "react";

export type Erc721ContextControllerProps = {
children: ReactNode;
};

export type Erc721ContextType = {
nftsByWallet: GetNFTsByWalletResult[][];
getNFTsByWallet: () => Promise<void>;
};
51 changes: 51 additions & 0 deletions app/src/context/evm/ERC721/Erc721ContextController.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from "react";
import { useAccount } from "wagmi";
import axios from "axios";
import { GetNFTsByWalletRequest, GetNFTsByWalletResult } from "api/evm/ERC721/types";
import _ from "lodash";

import { useRoutes } from "hooks/useRoutes/useRoutes";

import { Erc721ContextControllerProps, Erc721ContextType } from "./Erc721Context.types";
import { Erc721Context } from "./Erc721Context";

const groupNFTsByContract = (nfts: GetNFTsByWalletResult[]) => {
const group = _.groupBy(nfts, "tokenAddress");

console.log(_.map(group));

return _.map(group);
};

export const Erc721ContextController = ({ children }: Erc721ContextControllerProps) => {
const [nftsByWallet, setNftsByWallet] = React.useState<Erc721ContextType["nftsByWallet"]>([]);

const { address, chainId } = useAccount();
const routes = useRoutes();

const getNFTsByWallet = async () => {
try {
const data: GetNFTsByWalletRequest = {
chainId: chainId!,
walletAddress: address!,
};

const result = await axios.post<GetNFTsByWalletResult[]>(routes.api.evm.ERC721.getNFTsByWallet(), {
data,
});

console.log(result.data);

setNftsByWallet(groupNFTsByContract(result.data));
} catch (error) {
console.error(error);
}
};

const props: Erc721ContextType = {
getNFTsByWallet,
nftsByWallet,
};

return <Erc721Context.Provider value={props}>{children}</Erc721Context.Provider>;
};
13 changes: 13 additions & 0 deletions app/src/context/evm/ERC721/useErc721Context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { useContext } from "react";

import { Erc721Context } from "./Erc721Context";

export const useErc721Context = () => {
const context = useContext(Erc721Context);

if (context === undefined) {
throw new Error("useErc721Context must be used within a Erc721Context");
}

return context;
};
56 changes: 9 additions & 47 deletions app/src/hooks/useRoutes/useRoutes.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,14 @@
type RouteMap = {
home: () => string;
market: {
price: (args: { marketId: string }) => string;
};
api: {
promptWars: {
createGuestAccount: () => string;
create: () => string;
reveal: () => string;
resolve: () => string;
};
chat: {
dropboxESign: () => string;
openai: {
completionsAPI: () => string;
assistantsAPI: () => string;
};
googleai: {
completionsAPI: () => string;
};
};
};
dashboard: {
latestTrends: () => string;
promptWars: {
home: () => string;
previousMarkets: () => string;
market: (args: { marketId: string }) => string;
};
market: (args: { marketId: string }) => string;
};
};

export const routes: RouteMap = {
export const routes = {
home: () => `/`,
market: {
price: ({ marketId }) => `/market/price/${marketId}`,
profile: {
index: () => `/profile`,
},
api: {
evm: {
ERC721: {
getNFTsByWallet: () => `/api/evm/ERC721/get-nfts-by-wallet`,
},
},
promptWars: {
createGuestAccount: () => `/api/prompt-wars/create-guest-account`,
create: () => `/api/prompt-wars/create`,
Expand All @@ -55,15 +26,6 @@ export const routes: RouteMap = {
},
},
},
dashboard: {
latestTrends: () => `/`,
promptWars: {
home: () => `/`,
previousMarkets: () => `/previous-rounds`,
market: ({ marketId }) => `/${marketId}`,
},
market: ({ marketId }) => `/market/${marketId}`,
},
};

export const useRoutes: () => RouteMap = () => routes;
export const useRoutes = () => routes;
21 changes: 12 additions & 9 deletions app/src/layouts/home-layout/HomeLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ToastContextController } from "context/toast/ToastContextController";
import { ThemeContextController } from "context/theme/ThemeContextController";
import { EvmWalletSelectorContextController } from "context/evm/wallet-selector/EvmWalletSelectorContextController";
import { Footer } from "ui/footer/Footer";
import { Erc721ContextController } from "context/evm/ERC721/Erc721ContextController";

import { ChatLayoutProps } from "./HomeLayout.types";
import styles from "./HomeLayout.module.scss";
Expand All @@ -27,17 +28,19 @@ export const HomeLayout: React.FC<ChatLayoutProps> = ({ children }) => {
</Head>
<ThemeContextController>
<EvmWalletSelectorContextController>
<ToastContextController>
<div id="modal-root" />
<div id="dropdown-portal" />
<div className={clsx(styles["home-layout"])}>
<Navbar />
<Erc721ContextController>
<ToastContextController>
<div id="modal-root" />
<div id="dropdown-portal" />
<div className={clsx(styles["home-layout"])}>
<Navbar />

<MainPanel>{children}</MainPanel>
<MainPanel>{children}</MainPanel>

<Footer />
</div>
</ToastContextController>
<Footer />
</div>
</ToastContextController>
</Erc721ContextController>
</EvmWalletSelectorContextController>
</ThemeContextController>
</>
Expand Down
30 changes: 30 additions & 0 deletions app/src/pages/api/evm/ERC721/get-nfts-by-wallet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { NextApiRequest, NextApiResponse } from "next";

import logger from "providers/logger";
import moralis from "providers/moralis";

export default async function Fn(request: NextApiRequest, response: NextApiResponse) {
try {
await moralis.loadClient();
} catch (error) {
logger.error(error);
}

try {
const { data } = request.body;
logger.info(`api/evm/ERC721/get-nfts-by-wallet: ${JSON.stringify(data)}`);

const result = await moralis.client.EvmApi.nft.getWalletNFTs({
chain: data.chainId,
format: "decimal",
mediaItems: false,
address: data.walletAddress,
});

response.status(200).json(result.result);
} catch (error) {
logger.error(error);

response.status(500).json({ error: (error as Error).message });
}
}
11 changes: 11 additions & 0 deletions app/src/pages/api/evm/ERC721/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { EvmNft } from "@moralisweb3/common-evm-utils/lib";
import { MoralisDataObjectValue } from "@moralisweb3/common-core/lib";

export type GetNFTsByWalletResult = EvmNft & {
metadata: MoralisDataObjectValue & Record<string, any>;
};

export type GetNFTsByWalletRequest = {
walletAddress: string;
chainId: number;
};
37 changes: 37 additions & 0 deletions app/src/pages/profile/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { GetServerSidePropsContext, NextPage } from "next";
import { i18n, useTranslation } from "next-i18next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import Head from "next/head";

import { HomeLayout } from "layouts/home-layout/HomeLayout";
import { Collections } from "ui/lease721/profile/collections/Collections";

const Index: NextPage = () => {
const { t } = useTranslation("head");

return (
<HomeLayout>
<Head>
<title>{t("head.og.title")}</title>
<meta name="description" content={t("head.og.description")} />
<meta property="og:title" content={t("head.og.title")} />
<meta property="og:description" content={t("head.og.description")} />
<meta property="og:url" content="https://lease721.com/" />
</Head>

<Collections />
</HomeLayout>
);
};

export const getServerSideProps = async ({ locale }: GetServerSidePropsContext) => {
await i18n?.reloadResources();

return {
props: {
...(await serverSideTranslations(locale!, ["common", "head", "chat", "prompt-wars"])),
},
};
};

export default Index;
11 changes: 11 additions & 0 deletions app/src/providers/moralis/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import Moralis from "moralis";

const loadClient = async () =>
await Moralis.start({
apiKey: process.env.MORALIS_API_KEY,
});

export default {
loadClient,
client: Moralis,
};
13 changes: 13 additions & 0 deletions app/src/theme/_mixins.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@mixin hero {
&__hero {
@include atLargeTablet {
height: 50vh;
}
display: flex;
flex-direction: column;
justify-content: center;
height: 100vh;
text-align: left;
background-color: var(--color-background);
}
}
4 changes: 4 additions & 0 deletions app/src/ui/fileagent/navbar/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Typography } from "ui/typography/Typography";
import { Grid } from "ui/grid/Grid";
import { WalletSelector } from "ui/wallet-selector/WalletSelector";
import { Lease721Logo } from "ui/icons/Lease721Logo";
import { ProfileDropdown } from "ui/lease721/navbar/profile-dropdown/ProfileDropdown";

import { NavbarProps } from "./Navbar.types";
import styles from "./Navbar.module.scss";
Expand All @@ -29,6 +30,9 @@ export const Navbar: React.FC<NavbarProps> = ({ className }) => (
<div className={styles["navbar__right--item"]}>
<WalletSelector />
</div>
<div className={styles["navbar__right--item"]}>
<ProfileDropdown />
</div>
</div>
</Grid.Col>
</Grid.Row>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import "src/theme/base";

.profile-dropdown {
display: block;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export type Styles = {
"profile-dropdown": string;
"z-depth-0": string;
"z-depth-1": string;
"z-depth-1-half": string;
"z-depth-2": string;
"z-depth-3": string;
"z-depth-4": string;
"z-depth-5": string;
};

export type ClassNames = keyof Styles;

declare const styles: Styles;

export default styles;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { screen, render } from "tests";

import { ProfileDropdown } from "./ProfileDropdown";

describe("ProfileDropdown", () => {
it("renders children correctly", () => {
render(<ProfileDropdown>ProfileDropdown</ProfileDropdown>);

const element = screen.getByText("ProfileDropdown");

expect(element).toBeInTheDocument();
});
});
20 changes: 20 additions & 0 deletions app/src/ui/lease721/navbar/profile-dropdown/ProfileDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import clsx from "clsx";

import { Icon } from "ui/icon/Icon";
import { Typography } from "ui/typography/Typography";
import { useRoutes } from "hooks/useRoutes/useRoutes";

import { ProfileDropdownProps } from "./ProfileDropdown.types";
import styles from "./ProfileDropdown.module.scss";

export const ProfileDropdown: React.FC<ProfileDropdownProps> = ({ className }) => {
const routes = useRoutes();

return (
<div className={clsx(styles["profile-dropdown"], className)}>
<Typography.Link href={routes.profile.index()}>
<Icon name="icon-user" />
</Typography.Link>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { ReactNode } from "react";

export type ProfileDropdownProps = {
children?: ReactNode;
className?: string;
};
Loading

0 comments on commit 82ee50b

Please sign in to comment.