-
Notifications
You must be signed in to change notification settings - Fork 1
Description
https://potlock.notion.site/Simple-Lists-120c1f4ba97e8034a772f63b0b9f2335?pvs=4
// pages/_app.js
import { useEffect, useState } from 'react';
import { WalletSelector } from '@near-wallet-selector/core';
import { setupWalletSelector } from '@near-wallet-selector/core';
import { setupNearWallet } from '@near-wallet-selector/near-wallet';
import { setupMyNearWallet } from '@near-wallet-selector/my-near-wallet';
import { ThemeProvider } from '@emotion/react';
import { Global, css } from '@emotion/react';
import { theme } from '../styles/theme';
function MyApp({ Component, pageProps }) {
const [walletSelector, setWalletSelector] = useState(null);
useEffect(() => {
const initWalletSelector = async () => {
const selector = await setupWalletSelector({
network: 'mainnet',
modules: [setupNearWallet(), setupMyNearWallet()],
});
setWalletSelector(selector);
};
initWalletSelector();
}, []);
return (
<Global
styles={cssbody { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; }}
/>
<Component {...pageProps} walletSelector={walletSelector} />
);
}
export default MyApp;
// components/Navbar.js
import styled from '@emotion/styled';
import Link from 'next/link';
import { useWalletSelector } from '../hooks/useWalletSelector';
const NavbarContainer = styled.navdisplay: flex; justify-content: space-between; align-items: center; padding: 1rem; background-color: ${props => props.theme.colors.primary};;
const NavLink = styled(Link)color: white; text-decoration: none; margin-right: 1rem;;
const LoginButton = styled.buttonbackground-color: white; color: ${props => props.theme.colors.primary}; border: none; padding: 0.5rem 1rem; cursor: pointer;;
export function Navbar() {
const { selector, accountId, signIn, signOut } = useWalletSelector();
return (
My Lists
Explore
Create List
{accountId ? (
<>
{accountId}
Sign Out
</>
) : (
Login
)}
);
}
// pages/explore.js
import { useState, useEffect } from 'react';
import styled from '@emotion/styled';
import { Navbar } from '../components/Navbar';
import { ListCard } from '../components/ListCard';
import { getLists } from '../utils/nearInteractions';
const ExploreContainer = styled.divpadding: 2rem;;
const FilterContainer = styled.divmargin-bottom: 1rem;;
const ListContainer = styled.divdisplay: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 1rem;;
export default function Explore() {
const [lists, setLists] = useState([]);
const [filter, setFilter] = useState('recent');
useEffect(() => {
const fetchLists = async () => {
const fetchedLists = await getLists();
setLists(fetchedLists);
};
fetchLists();
}, []);
const sortedLists = [...lists].sort((a, b) => {
if (filter === 'recent') return b.id - a.id;
if (filter === 'upvotes') return b.upvotes - a.upvotes;
return 0;
});
return (
<>
Explore Lists
<select value={filter} onChange={(e) => setFilter(e.target.value)}>
Most Recent
Most Upvotes
{sortedLists.map((list) => (
))}
</>
);
}
// components/ListCard.js
import styled from '@emotion/styled';
import Link from 'next/link';
const Card = styled.divborder: 1px solid #ddd; border-radius: 4px; padding: 1rem;;
const Banner = styled.imgwidth: 100%; height: 100px; object-fit: cover;;
export function ListCard({ list }) {
return (
<Banner src={list.coverImgUrl || '/placeholder.jpg'} alt={list.name} />
{list.name}
Creator: {list.owner}
Upvotes: {list.upvotes}
{list.description}
Approved Projects: {list.approvedProjectsCount}
<Link href={
/list/${list.id}}>View List);
}
// pages/list/[id].js
import { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
import styled from '@emotion/styled';
import { Navbar } from '../../components/Navbar';
import { getList, updateRegistrationStatus, upvoteList } from '../../utils/nearInteractions';
import { useWalletSelector } from '../../hooks/useWalletSelector';
const ListContainer = styled.divpadding: 2rem;;
const ItemContainer = styled.divdisplay: flex; justify-content: space-between; align-items: center; padding: 0.5rem; border-bottom: 1px solid #ddd;;
export default function ListPage() {
const router = useRouter();
const { id } = router.query;
const [list, setList] = useState(null);
const { accountId, signIn } = useWalletSelector();
useEffect(() => {
if (id) {
const fetchList = async () => {
const fetchedList = await getList(id);
setList(fetchedList);
};
fetchList();
}
}, [id]);
const handleStatusChange = async (registrationId, newStatus) => {
if (!accountId) {
alert('Please sign in to update status');
return;
}
await updateRegistrationStatus(registrationId, newStatus);
// Refetch list to update UI
const updatedList = await getList(id);
setList(updatedList);
};
const handleUpvote = async () => {
if (!accountId) {
alert('Please sign in to upvote');
return;
}
await upvoteList(id);
// Refetch list to update UI
const updatedList = await getList(id);
setList(updatedList);
};
if (!list) return
return (
<>
{list.name}
{list.description}
Upvote ({list.upvotes})
Items
{list.items.map((item) => (
{item.value}
{(accountId === list.owner || list.admins.includes(accountId)) && (
<select
value={item.status}
onChange={(e) => handleStatusChange(item.id, e.target.value)}
>
Approved
Pending
Rejected
Blacklisted
)}
))}
</>
);
}
// utils/nearInteractions.js
import { connect, Contract, keyStores, WalletConnection } from 'near-api-js';
const CONTRACT_NAME = 'lists.potlock.near';
const getConfig = (env) => {
switch (env) {
case 'production':
case 'mainnet':
return {
networkId: 'mainnet',
nodeUrl: 'https://rpc.mainnet.near.org',
contractName: CONTRACT_NAME,
walletUrl: 'https://wallet.near.org',
helperUrl: 'https://helper.mainnet.near.org',
};
case 'development':
case 'testnet':
return {
networkId: 'testnet',
nodeUrl: 'https://rpc.testnet.near.org',
contractName: 'list.potlock.testnet',
walletUrl: 'https://wallet.testnet.near.org',
helperUrl: 'https://helper.testnet.near.org',
};
default:
throw Error(Unknown environment '${env}'.);
}
};
export async function initContract() {
const nearConfig = getConfig(process.env.NEAR_ENV || 'testnet');
const keyStore = new keyStores.BrowserLocalStorageKeyStore();
const near = await connect({ keyStore, ...nearConfig });
const walletConnection = new WalletConnection(near);
const contract = new Contract(walletConnection.account(), nearConfig.contractName, {
viewMethods: ['get_list', 'get_lists', 'get_registrations_for_list'],
changeMethods: ['create_list', 'update_registration', 'upvote'],
});
return { contract, walletConnection };
}
export async function getLists() {
const { contract } = await initContract();
return contract.get_lists({ from_index: 0, limit: 50 });
}
export async function getList(listId) {
const { contract } = await initContract();
return contract.get_list({ list_id: listId });
}
export async function updateRegistrationStatus(registrationId, newStatus) {
const { contract } = await initContract();
return contract.update_registration({ registration_id: registrationId, status: newStatus });
}
export async function upvoteList(listId) {
const { contract } = await initContract();
return contract.upvote({ list_id: listId });
}
// styles/theme.js
export const theme = {
colors: {
primary: '#0070f3',
secondary: '#ff4081',
background: '#f5f5f5',
text: '#333',
},
};
// hooks/useWalletSelector.js
import { useEffect, useState } from 'react';
export function useWalletSelector(selector) {
const [accountId, setAccountId] = useState(null);
useEffect(() => {
if (!selector) return;
const subscribeToWalletEvents = async () => {
const state = selector.store.getState();
setAccountId(state.accounts[0]?.accountId || null);
selector.on('accountsChanged', (accounts) => {
setAccountId(accounts[0]?.accountId || null);
});
};
subscribeToWalletEvents();
}, [selector]);
const signIn = () => {
selector.show();
};
const signOut = () => {
selector.signOut();
};
return { selector, accountId, signIn, signOut };
}
Metadata
Metadata
Assignees
Labels
Type
Projects
Status