From 523cfc86e54a8ff650d1476388c4d0d1339912fc Mon Sep 17 00:00:00 2001 From: colinmfoster4723 Date: Fri, 1 Nov 2024 17:19:21 -0500 Subject: [PATCH 1/7] Added pretty links for teams --- ui/components/layout/Card.tsx | 7 ++-- ui/lib/subscription/pretty-links.ts | 32 +++++++++++++++++++ ui/pages/network.tsx | 22 +++++++++++++ .../{[tokenId].tsx => [tokenIdOrName].tsx} | 25 ++++++++++++++- 4 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 ui/lib/subscription/pretty-links.ts rename ui/pages/team/{[tokenId].tsx => [tokenIdOrName].tsx} (96%) diff --git a/ui/components/layout/Card.tsx b/ui/components/layout/Card.tsx index 24ad1ebe..3d5f685a 100644 --- a/ui/components/layout/Card.tsx +++ b/ui/components/layout/Card.tsx @@ -4,7 +4,6 @@ import Image from 'next/image' import Link from 'next/link' import { useRouter } from 'next/router' import { ReactNode, useEffect, useState } from 'react' -import { MeshStandardMaterial } from 'three' import Frame from '../layout/Frame' import StandardButton from '../layout/StandardButton' @@ -26,6 +25,7 @@ interface CardProps { horizontalscroll?: boolean role?: string profile?: boolean + prettyLink?: string } export default function Card({ @@ -46,6 +46,7 @@ export default function Card({ role, horizontalscroll = false, profile = false, + prettyLink, }: CardProps) { icon = type === 'team' @@ -253,7 +254,9 @@ export default function Card({ onClick={async () => { setIsLoadingRoute(true) const route = await router.push( - `/${type === 'team' ? 'team' : 'citizen'}/${metadata.id}` + `/${type === 'team' ? 'team' : 'citizen'}/${ + prettyLink || metadata.id + }` ) if (route) setIsLoadingRoute(false) }} diff --git a/ui/lib/subscription/pretty-links.ts b/ui/lib/subscription/pretty-links.ts new file mode 100644 index 00000000..6d06572f --- /dev/null +++ b/ui/lib/subscription/pretty-links.ts @@ -0,0 +1,32 @@ +//Generate pretty links for teams or citizens, name => tokenId + +type PrettyLinkData = { + name: string + id: string | number +} + +export function generatePrettyLinks(prettyLinkData: PrettyLinkData[]) { + // Create an object to store the pretty links with their indices + const prettyLinks: Record = {} + const idToPrettyLink: Record = {} + + // Loop through the citizens or teams + for (let i = 0; i < prettyLinkData.length; i++) { + // Take the name and replace spaces with hyphens + const name = prettyLinkData[i]?.name.toLowerCase() as string + const id = prettyLinkData[i]?.id as string + let prettyLink = name.replace(/\s+/g, '-') + + // Ensure unique keys by appending the index if necessary + while (prettyLinks.hasOwnProperty(prettyLink)) { + prettyLink = `${prettyLink}-${i}` + } + + // Map the pretty link to its index + prettyLinks[prettyLink] = id + idToPrettyLink[id] = prettyLink + } + + // Return the object + return { prettyLinks, idToPrettyLink } +} diff --git a/ui/pages/network.tsx b/ui/pages/network.tsx index 0b849f82..fd02dc9e 100644 --- a/ui/pages/network.tsx +++ b/ui/pages/network.tsx @@ -10,6 +10,7 @@ import Image from 'next/image' import Link from 'next/link' import { useRouter } from 'next/router' import React, { useState, useEffect, useCallback } from 'react' +import { generatePrettyLinks } from '@/lib/subscription/pretty-links' import { useChainDefault } from '@/lib/thirdweb/hooks/useChainDefault' import { initSDK } from '@/lib/thirdweb/thirdweb' import { useShallowQueryRoute } from '@/lib/utils/hooks' @@ -27,11 +28,16 @@ import Tab from '@/components/layout/Tab' type NetworkProps = { filteredTeams: NFT[] filteredCitizens: NFT[] + prettyLinks: { + team: Record + citizen: Record + } } export default function Network({ filteredTeams, filteredCitizens, + prettyLinks, }: NetworkProps) { const router = useRouter() const shallowQueryRoute = useShallowQueryRoute() @@ -203,6 +209,7 @@ export default function Network({ owner={nft.owner} type={type} hovertext="Explore Profile" + prettyLink={prettyLinks?.[type]?.[nft.metadata.id]} /> ) @@ -330,10 +337,25 @@ export async function getStaticProps() { } ) + //Generate pretty links + const prettyLinks = { + team: {}, + citizen: {}, + } + const teamPrettyLinkData = filteredValidTeams.map((nft: any) => ({ + name: nft?.metadata?.name, + id: nft?.metadata?.id, + })) + const { idToPrettyLink: teamIdToPrettyLink } = + generatePrettyLinks(teamPrettyLinkData) + + prettyLinks.team = teamIdToPrettyLink + return { props: { filteredTeams: filteredValidTeams.reverse(), filteredCitizens: filteredValidCitizens.reverse(), + prettyLinks, }, revalidate: 60, } diff --git a/ui/pages/team/[tokenId].tsx b/ui/pages/team/[tokenIdOrName].tsx similarity index 96% rename from ui/pages/team/[tokenId].tsx rename to ui/pages/team/[tokenIdOrName].tsx index 2865784a..55d1e8b8 100644 --- a/ui/pages/team/[tokenId].tsx +++ b/ui/pages/team/[tokenIdOrName].tsx @@ -23,6 +23,8 @@ import { JOBS_TABLE_ADDRESSES, MOONEY_ADDRESSES, MARKETPLACE_TABLE_ADDRESSES, + TEAM_TABLE_ADDRESSES, + TABLELAND_ENDPOINT, } from 'const/config' import { blockedTeams } from 'const/whitelist' import { GetServerSideProps } from 'next' @@ -33,6 +35,7 @@ import { useContext, useEffect, useState } from 'react' import toast from 'react-hot-toast' import CitizenContext from '@/lib/citizen/citizen-context' import { useSubHats } from '@/lib/hats/useSubHats' +import { generatePrettyLinks } from '@/lib/subscription/pretty-links' import { useTeamData } from '@/lib/team/useTeamData' import ChainContext from '@/lib/thirdweb/chain-context' import { useChainDefault } from '@/lib/thirdweb/hooks/useChainDefault' @@ -559,10 +562,30 @@ export default function TeamDetailPage({ tokenId, nft, imageIpfsLink }: any) { } export const getServerSideProps: GetServerSideProps = async ({ params }) => { - const tokenId: any = params?.tokenId + const tokenIdOrName: any = params?.tokenIdOrName const chain = process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? Arbitrum : Sepolia const sdk = initSDK(chain) + + //Generate pretty links + const teamTableContract = await sdk.getContract( + TEAM_TABLE_ADDRESSES[chain.slug] + ) + const teamTableName = await teamTableContract.call('getTableName') + const statement = `SELECT name, id FROM ${teamTableName}` + const allTeamsRes = await fetch( + `${TABLELAND_ENDPOINT}?statement=${statement}` + ) + const allTeams = await allTeamsRes.json() + const { prettyLinks } = generatePrettyLinks(allTeams) + + let tokenId + if (!Number.isNaN(Number(tokenIdOrName))) { + tokenId = tokenIdOrName + } else { + tokenId = prettyLinks[tokenIdOrName] + } + const teamContract = await sdk.getContract(TEAM_ADDRESSES[chain.slug]) const nft = await teamContract.erc721.get(tokenId) From 58184a133bfe36b970640433190803f739632ba3 Mon Sep 17 00:00:00 2001 From: colinmfoster4723 Date: Mon, 4 Nov 2024 13:35:26 -0600 Subject: [PATCH 2/7] Added pretty links for citizens --- .../{[tokenId].tsx => [tokenIdOrName].tsx} | 26 ++++++++++++++++++- ui/pages/network.tsx | 10 +++++++ 2 files changed, 35 insertions(+), 1 deletion(-) rename ui/pages/citizen/{[tokenId].tsx => [tokenIdOrName].tsx} (96%) diff --git a/ui/pages/citizen/[tokenId].tsx b/ui/pages/citizen/[tokenIdOrName].tsx similarity index 96% rename from ui/pages/citizen/[tokenId].tsx rename to ui/pages/citizen/[tokenIdOrName].tsx index caeba896..3479503f 100644 --- a/ui/pages/citizen/[tokenId].tsx +++ b/ui/pages/citizen/[tokenIdOrName].tsx @@ -12,7 +12,9 @@ import { Arbitrum, Sepolia } from '@thirdweb-dev/chains' import { ThirdwebNftMedia, useAddress, useContract } from '@thirdweb-dev/react' import { CITIZEN_ADDRESSES, + CITIZEN_TABLE_ADDRESSES, MOONEY_ADDRESSES, + TABLELAND_ENDPOINT, TEAM_ADDRESSES, VMOONEY_ADDRESSES, } from 'const/config' @@ -26,6 +28,7 @@ import { useContext, useEffect, useState } from 'react' import toast from 'react-hot-toast' import { useCitizenData } from '@/lib/citizen/useCitizenData' import { useTeamWearer } from '@/lib/hats/useTeamWearer' +import { generatePrettyLinks } from '@/lib/subscription/pretty-links' import { useTeamData } from '@/lib/team/useTeamData' import ChainContext from '@/lib/thirdweb/chain-context' import { useHandleRead } from '@/lib/thirdweb/hooks' @@ -506,10 +509,31 @@ export default function CitizenDetailPage({ } export const getServerSideProps: GetServerSideProps = async ({ params }) => { - const tokenId: any = params?.tokenId + const tokenIdOrName: any = params?.tokenIdOrName const chain = process.env.NEXT_PUBLIC_CHAIN === 'mainnet' ? Arbitrum : Sepolia const sdk = initSDK(chain) + + const citizenTableContract = await sdk?.getContract( + CITIZEN_TABLE_ADDRESSES[chain.slug] + ) + const citizenTableName = await citizenTableContract?.call('getTableName') + + const statement = `SELECT name, id FROM ${citizenTableName}` + const allCitizensRes = await fetch( + `${TABLELAND_ENDPOINT}?statement=${statement}` + ) + const allCitizens = await allCitizensRes.json() + + const { prettyLinks } = generatePrettyLinks(allCitizens) + + let tokenId + if (!Number.isNaN(Number(tokenIdOrName))) { + tokenId = tokenIdOrName + } else { + tokenId = prettyLinks[tokenIdOrName] + } + const teamContract = await sdk.getContract(CITIZEN_ADDRESSES[chain.slug]) const nft = await teamContract.erc721.get(tokenId) diff --git a/ui/pages/network.tsx b/ui/pages/network.tsx index fd02dc9e..1c1a1409 100644 --- a/ui/pages/network.tsx +++ b/ui/pages/network.tsx @@ -351,6 +351,16 @@ export async function getStaticProps() { prettyLinks.team = teamIdToPrettyLink + const citizenPrettyLinkData = filteredValidCitizens.map((nft: any) => ({ + name: nft?.metadata?.name, + id: nft?.metadata?.id, + })) + const { idToPrettyLink: citizenIdToPrettyLink } = generatePrettyLinks( + citizenPrettyLinkData + ) + + prettyLinks.citizen = citizenIdToPrettyLink + return { props: { filteredTeams: filteredValidTeams.reverse(), From 2d82fee562dd328cdd33db68b46cd001d3ff3555 Mon Sep 17 00:00:00 2001 From: colinmfoster4723 Date: Mon, 4 Nov 2024 15:03:15 -0600 Subject: [PATCH 3/7] Adjusted pretty link --- ui/lib/subscription/pretty-links.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/ui/lib/subscription/pretty-links.ts b/ui/lib/subscription/pretty-links.ts index 6d06572f..9382b6a7 100644 --- a/ui/lib/subscription/pretty-links.ts +++ b/ui/lib/subscription/pretty-links.ts @@ -1,16 +1,12 @@ -//Generate pretty links for teams or citizens, name => tokenId - type PrettyLinkData = { name: string id: string | number } export function generatePrettyLinks(prettyLinkData: PrettyLinkData[]) { - // Create an object to store the pretty links with their indices const prettyLinks: Record = {} const idToPrettyLink: Record = {} - // Loop through the citizens or teams for (let i = 0; i < prettyLinkData.length; i++) { // Take the name and replace spaces with hyphens const name = prettyLinkData[i]?.name.toLowerCase() as string @@ -19,10 +15,9 @@ export function generatePrettyLinks(prettyLinkData: PrettyLinkData[]) { // Ensure unique keys by appending the index if necessary while (prettyLinks.hasOwnProperty(prettyLink)) { - prettyLink = `${prettyLink}-${i}` + prettyLink = `${prettyLink}-${id}` } - // Map the pretty link to its index prettyLinks[prettyLink] = id idToPrettyLink[id] = prettyLink } From 50da2a2d5ece494a0e923c1c4c1346d438a34792 Mon Sep 17 00:00:00 2001 From: colinmfoster4723 Date: Mon, 4 Nov 2024 18:00:57 -0600 Subject: [PATCH 4/7] Added name check --- ui/lib/subscription/pretty-links.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/lib/subscription/pretty-links.ts b/ui/lib/subscription/pretty-links.ts index 9382b6a7..75c1635f 100644 --- a/ui/lib/subscription/pretty-links.ts +++ b/ui/lib/subscription/pretty-links.ts @@ -9,7 +9,7 @@ export function generatePrettyLinks(prettyLinkData: PrettyLinkData[]) { for (let i = 0; i < prettyLinkData.length; i++) { // Take the name and replace spaces with hyphens - const name = prettyLinkData[i]?.name.toLowerCase() as string + const name = prettyLinkData[i]?.name?.toLowerCase() as string const id = prettyLinkData[i]?.id as string let prettyLink = name.replace(/\s+/g, '-') From 6e42c4737f514d2c4e4cdfd12426bbd8e1b26626 Mon Sep 17 00:00:00 2001 From: colinmfoster4723 Date: Mon, 4 Nov 2024 18:06:32 -0600 Subject: [PATCH 5/7] Added name and id check --- ui/lib/subscription/pretty-links.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/ui/lib/subscription/pretty-links.ts b/ui/lib/subscription/pretty-links.ts index 75c1635f..60dfedfa 100644 --- a/ui/lib/subscription/pretty-links.ts +++ b/ui/lib/subscription/pretty-links.ts @@ -8,18 +8,20 @@ export function generatePrettyLinks(prettyLinkData: PrettyLinkData[]) { const idToPrettyLink: Record = {} for (let i = 0; i < prettyLinkData.length; i++) { - // Take the name and replace spaces with hyphens - const name = prettyLinkData[i]?.name?.toLowerCase() as string - const id = prettyLinkData[i]?.id as string - let prettyLink = name.replace(/\s+/g, '-') + const name = prettyLinkData[i]?.name?.toLowerCase() + const id = prettyLinkData[i]?.id - // Ensure unique keys by appending the index if necessary - while (prettyLinks.hasOwnProperty(prettyLink)) { - prettyLink = `${prettyLink}-${id}` - } + if (name && id) { + let prettyLink = name.replace(/\s+/g, '-') + + // Ensure unique keys by appending the index if necessary + while (prettyLinks.hasOwnProperty(prettyLink)) { + prettyLink = `${prettyLink}-${id}` + } - prettyLinks[prettyLink] = id - idToPrettyLink[id] = prettyLink + prettyLinks[prettyLink] = id + idToPrettyLink[id] = prettyLink + } } // Return the object From 8a0173ac57d1586f1dba8d662821457aa3366698 Mon Sep 17 00:00:00 2001 From: colinmfoster4723 Date: Fri, 8 Nov 2024 12:46:31 -0600 Subject: [PATCH 6/7] Added test for pretty links --- .../subscription/pretty-links.cy.tsx | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 ui/cypress/integration/subscription/pretty-links.cy.tsx diff --git a/ui/cypress/integration/subscription/pretty-links.cy.tsx b/ui/cypress/integration/subscription/pretty-links.cy.tsx new file mode 100644 index 00000000..ddf8152e --- /dev/null +++ b/ui/cypress/integration/subscription/pretty-links.cy.tsx @@ -0,0 +1,58 @@ +import { generatePrettyLinks } from '@/lib/subscription/pretty-links' + +describe('generatePrettyLinks', () => { + it('Should generate pretty links correctly', () => { + const input: any = [ + { name: 'Test Link', id: 1 }, + { name: 'Another Link', id: 2 }, + { name: 'Test Link', id: 3 }, + ] + + const expectedOutput = { + prettyLinks: { + 'test-link': 1, + 'another-link': 2, + 'test-link-3': 3, + }, + idToPrettyLink: { + 1: 'test-link', + 2: 'another-link', + 3: 'test-link-3', + }, + } + + const result = generatePrettyLinks(input) + expect(result).to.deep.equal(expectedOutput) + }) + + it('Should handle empty inputs', () => { + const input: any = [] + const expectedOutput = { + prettyLinks: {}, + idToPrettyLink: {}, + } + + const result = generatePrettyLinks(input) + expect(result).to.deep.equal(expectedOutput) + }) + + it('Should handle input with missing names or ids', () => { + const input: any = [ + { name: '', id: 1 }, + { name: 'Valid Link', id: null }, + { name: 'Another Valid Link', id: 2 }, + ] + + const expectedOutput = { + prettyLinks: { + 'another-valid-link': 2, + }, + idToPrettyLink: { + 2: 'another-valid-link', + }, + } + + const result = generatePrettyLinks(input) + expect(result).to.deep.equal(expectedOutput) + }) +}) From dfbed21c2a9485d73ca73ed9cabdb49ebf46d1ca Mon Sep 17 00:00:00 2001 From: colinmfoster4723 Date: Fri, 8 Nov 2024 15:10:36 -0600 Subject: [PATCH 7/7] Added allHaveTokenId option to pretty link generation --- .../subscription/pretty-links.cy.tsx | 24 +++++++++++++++++++ ui/lib/subscription/pretty-links.ts | 18 ++++++++++---- ui/pages/citizen/[tokenIdOrName].tsx | 4 +++- ui/pages/network.tsx | 7 +++--- 4 files changed, 45 insertions(+), 8 deletions(-) diff --git a/ui/cypress/integration/subscription/pretty-links.cy.tsx b/ui/cypress/integration/subscription/pretty-links.cy.tsx index ddf8152e..6f710663 100644 --- a/ui/cypress/integration/subscription/pretty-links.cy.tsx +++ b/ui/cypress/integration/subscription/pretty-links.cy.tsx @@ -25,6 +25,30 @@ describe('generatePrettyLinks', () => { expect(result).to.deep.equal(expectedOutput) }) + it('Should generate all pretty links with tokenId appended', () => { + const input: any = [ + { name: 'Test Link', id: 1 }, + { name: 'Another Link', id: 2 }, + { name: 'Test Link', id: 3 }, + ] + + const expectedOutput = { + prettyLinks: { + 'test-link-1': 1, + 'another-link-2': 2, + 'test-link-3': 3, + }, + idToPrettyLink: { + 1: 'test-link-1', + 2: 'another-link-2', + 3: 'test-link-3', + }, + } + + const result = generatePrettyLinks(input, { allHaveTokenId: true }) + expect(result).to.deep.equal(expectedOutput) + }) + it('Should handle empty inputs', () => { const input: any = [] const expectedOutput = { diff --git a/ui/lib/subscription/pretty-links.ts b/ui/lib/subscription/pretty-links.ts index 60dfedfa..64c27d3c 100644 --- a/ui/lib/subscription/pretty-links.ts +++ b/ui/lib/subscription/pretty-links.ts @@ -3,7 +3,14 @@ type PrettyLinkData = { id: string | number } -export function generatePrettyLinks(prettyLinkData: PrettyLinkData[]) { +type Options = { + allHaveTokenId?: boolean +} + +export function generatePrettyLinks( + prettyLinkData: PrettyLinkData[], + options: Options = {} +) { const prettyLinks: Record = {} const idToPrettyLink: Record = {} @@ -11,12 +18,15 @@ export function generatePrettyLinks(prettyLinkData: PrettyLinkData[]) { const name = prettyLinkData[i]?.name?.toLowerCase() const id = prettyLinkData[i]?.id - if (name && id) { + if (name && id !== null && id !== undefined) { let prettyLink = name.replace(/\s+/g, '-') - // Ensure unique keys by appending the index if necessary - while (prettyLinks.hasOwnProperty(prettyLink)) { + if (options?.allHaveTokenId) { prettyLink = `${prettyLink}-${id}` + } else { + while (prettyLinks.hasOwnProperty(prettyLink)) { + prettyLink = `${prettyLink}-${id}` + } } prettyLinks[prettyLink] = id diff --git a/ui/pages/citizen/[tokenIdOrName].tsx b/ui/pages/citizen/[tokenIdOrName].tsx index 3479503f..7756bbe5 100644 --- a/ui/pages/citizen/[tokenIdOrName].tsx +++ b/ui/pages/citizen/[tokenIdOrName].tsx @@ -525,7 +525,9 @@ export const getServerSideProps: GetServerSideProps = async ({ params }) => { ) const allCitizens = await allCitizensRes.json() - const { prettyLinks } = generatePrettyLinks(allCitizens) + const { prettyLinks } = generatePrettyLinks(allCitizens, { + allHaveTokenId: true, + }) let tokenId if (!Number.isNaN(Number(tokenIdOrName))) { diff --git a/ui/pages/network.tsx b/ui/pages/network.tsx index 1c1a1409..dcb7f3fa 100644 --- a/ui/pages/network.tsx +++ b/ui/pages/network.tsx @@ -342,7 +342,7 @@ export async function getStaticProps() { team: {}, citizen: {}, } - const teamPrettyLinkData = filteredValidTeams.map((nft: any) => ({ + const teamPrettyLinkData = teams.map((nft: any) => ({ name: nft?.metadata?.name, id: nft?.metadata?.id, })) @@ -351,12 +351,13 @@ export async function getStaticProps() { prettyLinks.team = teamIdToPrettyLink - const citizenPrettyLinkData = filteredValidCitizens.map((nft: any) => ({ + const citizenPrettyLinkData = citizens.map((nft: any) => ({ name: nft?.metadata?.name, id: nft?.metadata?.id, })) const { idToPrettyLink: citizenIdToPrettyLink } = generatePrettyLinks( - citizenPrettyLinkData + citizenPrettyLinkData, + { allHaveTokenId: true } ) prettyLinks.citizen = citizenIdToPrettyLink