Skip to content

Commit

Permalink
setting up hooks for marketplace contract
Browse files Browse the repository at this point in the history
  • Loading branch information
LucasCodr committed Jul 30, 2022
1 parent a834368 commit 2ae57cd
Show file tree
Hide file tree
Showing 9 changed files with 466 additions and 14 deletions.
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NEXT_PUBLIC_CONTRACT_NAME=mycontract.lucassouza.testnet
9 changes: 9 additions & 0 deletions global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { Contract, WalletConnection } from 'near-api-js'

declare global {
interface Window {
walletConnection: WalletConnection
accountId: any
contract: Contract
}
}
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@
"@mantine/hooks": "^5.0.2",
"@mantine/next": "^5.0.2",
"@tabler/icons": "^1.78.1",
"near-api-js": "^0.45.1",
"next": "12.2.3",
"react": "18.2.0",
"react-dom": "18.2.0"
"react-dom": "18.2.0",
"uuid": "^8.3.2"
},
"devDependencies": {
"@types/node": "18.6.2",
"@types/react": "18.0.15",
"@types/react-dom": "18.0.6",
"@types/uuid": "^8.3.4",
"eslint": "8.20.0",
"eslint-config-next": "12.2.3",
"eslint-config-prettier": "^8.5.0",
Expand Down
68 changes: 57 additions & 11 deletions src/components/AppHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,29 @@
import React from 'react'
import { ActionIcon, createStyles, Header, Title, useMantineColorScheme } from '@mantine/core'
import { IconMoonStars, IconSun } from '@tabler/icons'
import {
ActionIcon,
Avatar,
Button,
createStyles,
Group,
Header,
Menu,
Text,
useMantineColorScheme,
} from '@mantine/core'
import { IconDoorExit, IconMoonStars, IconSun, IconWallet } from '@tabler/icons'
import { useWallet } from '../hooks/useWallet'

const useStyles = createStyles({
header: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
},
avatar: {
':hover': {
cursor: 'pointer',
},
},
})

const AppHeader: React.FC = () => {
Expand All @@ -16,17 +32,47 @@ const AppHeader: React.FC = () => {

const { classes } = useStyles()

const { login, signOut, account } = useWallet({ env: 'testnet' })

return (
<Header height={70} p="xs" className={classes.header}>
<Title order={2}>NEAR Marketplace</Title>
<ActionIcon
variant="outline"
color={dark ? 'yellow' : 'blue'}
onClick={() => toggleColorScheme()}
title="Toggle color scheme"
>
{dark ? <IconSun size={18} /> : <IconMoonStars size={18} />}
</ActionIcon>
<Text size={26} weight="bold" variant="gradient">
NEAR Marketplace
</Text>

<Group>
<ActionIcon
variant="transparent"
color={dark ? 'yellow' : 'dark'}
onClick={() => toggleColorScheme()}
title="Toggle color scheme"
size="md"
>
{dark ? <IconSun /> : <IconMoonStars />}
</ActionIcon>

{account?.accountId ? (
<Group spacing="xs">
<Text>{account.accountId}</Text>
<Menu shadow="md" width={200}>
<Menu.Target>
<Avatar alt={account.accountId} radius="xl" className={classes.avatar} />
</Menu.Target>

<Menu.Dropdown>
<Menu.Label>Application</Menu.Label>
<Menu.Item color="red" icon={<IconDoorExit size={14} />} onClick={signOut}>
Sign out
</Menu.Item>
</Menu.Dropdown>
</Menu>
</Group>
) : (
<Button variant="outline" leftIcon={<IconWallet />} onClick={login}>
Connect Wallet
</Button>
)}
</Group>
</Header>
)
}
Expand Down
73 changes: 73 additions & 0 deletions src/hooks/useMarketplace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { useWallet } from './useWallet'
import { useCallback, useMemo } from 'react'
import { Contract } from 'near-api-js'
import { v4 as uuid4 } from 'uuid'
import { parseNearAmount } from 'near-api-js/lib/utils/format'

interface UseMarketplaceProps {
env: 'mainnet' | 'testnet'
gasPrice?: number
}

export interface Product {
id: string
name: string
description: string
image: string
location: string
price: number
owner: string
sold: number
}

type MarketplaceContract = Contract & {
getProduct: (id: { id: string }) => Promise<Product | null>
getProducts: () => Promise<Product[]>

buyProduct: (productId: { productId: string }, gas?: number, price?: number) => Promise<void>
setProduct: (product: { product: Product }) => Promise<void>
}

const GAS = 100000000000000

export const useMarketplace = ({ env, gasPrice }: UseMarketplaceProps) => {
const { account } = useWallet({ env })

const contract = useMemo(() => {
if (!account?.accountId) return null

return new Contract(account, process.env.NEXT_PUBLIC_CONTRACT_NAME as string, {
viewMethods: ['getProduct', 'getProducts'],
changeMethods: ['buyProduct', 'setProduct'],
}) as MarketplaceContract
}, [account])

const createProduct = useCallback(
(newProduct: Product) => {
const product = Object.assign(newProduct, {
id: uuid4(),
price: parseNearAmount(newProduct.price.toString()),
})

return contract?.setProduct({ product })
},
[contract]
)

const getProducts = useCallback(async () => {
return contract?.getProducts()
}, [contract])

const buyProduct = useCallback(
(id: string, price: number) => {
return contract?.buyProduct({ productId: id }, gasPrice || GAS, price)
},
[contract, gasPrice]
)

return {
createProduct,
getProducts,
buyProduct,
}
}
61 changes: 61 additions & 0 deletions src/hooks/useWallet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { connect, keyStores, Near, WalletConnection } from 'near-api-js'
import { useCallback, useEffect, useMemo, useState } from 'react'
import environment from '../utils/config'
import { formatNearAmount } from 'near-api-js/lib/utils/format'

interface UseWalletProps {
env: 'mainnet' | 'testnet'
}

export const useWallet = ({ env }: UseWalletProps) => {
const [near, setNear] = useState<Near | null>(null)
const [balance, setBalance] = useState<string | null>(null)

const nearEnv = environment(env)

useEffect(() => {
if (near) return

connect(
Object.assign(nearEnv, {
keyStore: new keyStores.BrowserLocalStorageKeyStore(),
})
).then((data) => setNear(data))
}, [setNear, nearEnv, near])

const walletConnection = useCallback(() => {
return near && new WalletConnection(near, 'my-app')
}, [near])

const account = useMemo(() => {
return walletConnection()?.account()
}, [walletConnection])

useEffect(() => {
;(async () => {
if (!account?.accountId) return

const response = await walletConnection()?.account().getAccountBalance()

if (!response) return

return setBalance(formatNearAmount(response.total, 2))
})()
}, [walletConnection, account?.accountId, setBalance])

const login = () =>
walletConnection()?.requestSignIn({ contractId: process.env.NEXT_PUBLIC_CONTRACT_NAME })

const signOut = () => {
walletConnection()?.signOut()
window.location.reload()
}

return {
account,
balance,
login,
signOut,
nearEnv,
}
}
61 changes: 60 additions & 1 deletion src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,66 @@
import type { NextPage } from 'next'
import { Product, useMarketplace } from '../hooks/useMarketplace'
import { useState } from 'react'
import { Badge, Button, Card, Group, Image, SimpleGrid, Text } from '@mantine/core'
import { formatNearAmount } from 'near-api-js/lib/utils/format'
import { IconMapPin } from '@tabler/icons'

const Home: NextPage = () => {
return <h1 title="sasas">Hello world</h1>
const [products, setProducts] = useState<Product[]>([])
const { getProducts } = useMarketplace({ env: 'testnet' })

getProducts().then((data) => setProducts(data ?? []))

console.log(products)

return products.length ? (
<SimpleGrid
breakpoints={[
{ minWidth: 980, cols: 4, spacing: 'md' },
{ maxWidth: 755, cols: 2, spacing: 'sm' },
{ maxWidth: 600, cols: 1, spacing: 'sm' },
]}
>
{products.map((product) => (
<Card key={product.id} shadow="sm" p="lg" radius="md" withBorder>
<Card.Section>
<Image src={product.image} height={160} alt="Norway" />
</Card.Section>

<Group position="apart" mt="md" mb="xs">
<Text weight={500}>{product.name}</Text>
<Badge color="red" variant="light">
{formatNearAmount(product.sold.toString(), 2)} SOLD
</Badge>
</Group>

<Text size="sm" color="dimmed">
{product.description}
</Text>

<Group position="apart" mt="md" mb="xs">
<Text size="sm" weight="bold">
by {product.owner}
</Text>
<Group spacing={4}>
<IconMapPin size={15} />
<Text align="end" size="xs">
{product.location}
</Text>
</Group>
</Group>

<Button variant="gradient" color="blue" fullWidth mt="md" radius="md">
Buy for {formatNearAmount(product.price.toString(), 2)} NEAR
</Button>
</Card>
))}
</SimpleGrid>
) : (
<Text size={36} align="center">
Connect your wallet
</Text>
)
}

export default Home
28 changes: 28 additions & 0 deletions src/utils/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { ConnectConfig } from 'near-api-js'

export const CONTRACT_NAME = process.env.NEXT_PUBLIC_CONTRACT_NAME as string

function environment(env: 'mainnet' | 'testnet'): ConnectConfig {
switch (env) {
case 'mainnet':
return {
networkId: 'mainnet',
nodeUrl: 'https://rpc.mainnet.near.org',
walletUrl: 'https://wallet.near.org',
helperUrl: 'https://helper.mainnet.near.org',
headers: {},
}
case 'testnet':
return {
networkId: 'testnet',
nodeUrl: 'https://rpc.testnet.near.org',
walletUrl: 'https://wallet.testnet.near.org',
helperUrl: 'https://helper.testnet.near.org',
headers: {},
}
default:
throw Error(`Unknown environment '${env}'.`)
}
}

export default environment
Loading

1 comment on commit 2ae57cd

@vercel
Copy link

@vercel vercel bot commented on 2ae57cd Jul 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.