Skip to content

[TEMPLATE-REQUEST] Simple Lists #21

@codingshot

Description

@codingshot

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

Loading...
;

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

No one assigned

    Labels

    Type

    No type

    Projects

    Status

    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions