Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add category pages #18

Merged
merged 3 commits into from
Dec 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions app/(pages)/explore/[category]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { Metadata } from 'next'
import { draftMode } from 'next/headers'
import { notFound } from 'next/navigation'
import { fetchTokenPagesForCategory } from '../../../../contentful/tokenPage'
import {
fetchTokenCategoryPage,
fetchTokenCategoryPages,
} from '../../../../contentful/tokenCategoryPage'
import { fetchMangoTokenData } from '../../../utils/mango'
import Category from '../../../components/explore/Category'

interface TokenCategoryPageParams {
category: string
}

interface TokenCategoryPageProps {
params: TokenCategoryPageParams
}

// Tell Next.js about all our category pages so
// they can be statically generated at build time.
export async function generateStaticParams(): Promise<
TokenCategoryPageParams[]
> {
const tokenCategoryPages = await fetchTokenCategoryPages({ preview: false })

return tokenCategoryPages.map((page) => ({ category: page.slug }))
}

// For each category page, tell Next.js which metadata
// (e.g. page title) to display.
export async function generateMetadata(
{ params }: TokenCategoryPageProps,
// parent: ResolvingMetadata,
): Promise<Metadata> {
const tokenCategoryPage = await fetchTokenCategoryPage({
slug: params.category,
preview: draftMode().isEnabled,
})

if (!tokenCategoryPage) {
return notFound()
}
return {
title: tokenCategoryPage?.seoTitle,
description: tokenCategoryPage?.seoDescription,
}
}

async function TokenCategoryPage({ params }: TokenCategoryPageProps) {
const tokenCategoryPageData = await fetchTokenCategoryPage({
slug: params.category,
preview: draftMode().isEnabled,
})

if (!tokenCategoryPageData) {
return notFound()
}

const { category } = tokenCategoryPageData

// fetch token pages from contentful where the entry contains the category (tag)
const tokensForCategory = await fetchTokenPagesForCategory({
category,
preview: draftMode().isEnabled,
})

// fetch mango token data for each token page
const promises = tokensForCategory.map((token) =>
fetchMangoTokenData(token.mint),
)
const mangoTokensData = await Promise.all(promises)

return (
<Category
categoryPageData={tokenCategoryPageData}
tokensForCategory={tokensForCategory}
mangoTokensData={mangoTokensData}
/>
)
}

export default TokenCategoryPage
22 changes: 0 additions & 22 deletions app/(pages)/explore/[slug]/layout.tsx

This file was deleted.

24 changes: 24 additions & 0 deletions app/(pages)/token/[slug]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ArrowLeftIcon } from '@heroicons/react/20/solid'
import Link from 'next/link'

export default function TokenPageLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="px-6 lg:px-20 pb-12 pt-8 max-w-[1280px] mx-auto min-h-screen">
<div className="-mt-6">
<Link
className="text-th-fgd-4 flex items-center space-x-1"
href="/explore"
shallow
>
<ArrowLeftIcon className="h-5 w-5" />
<span>View all tokens</span>
</Link>
<div className="mt-8">{children}</div>
</div>
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
fetchTokenPages,
} from '../../../../contentful/tokenPage'
import { CUSTOM_TOKEN_ICONS } from '../../../utils/constants'
import TokenAbout from '../../../components/explore/token-page/TokenAbout'
import RichTextDisplay from '../../../components/shared/RichTextDisplay'
import { fetchMangoMarketData, fetchMangoTokenData } from '../../../utils/mango'
import { MangoMarketsData, MangoTokenData } from '../../../types/mango'
import Image from 'next/image'
Expand Down Expand Up @@ -136,7 +136,7 @@ async function TokenPage({ params }: TokenPageProps) {
<TokenInfo
coingeckoData={coingeckoData}
tokenPageData={tokenPageData}
volume={birdeyeData?.v24hUSD}
birdeyeData={birdeyeData}
/>
</div>
</div>
Expand All @@ -151,7 +151,7 @@ async function TokenPage({ params }: TokenPageProps) {
{description ? (
<div className={SECTION_WRAPPER_CLASSES}>
<h2 className="mb-4 text-2xl">{`About ${tokenName}`}</h2>
<TokenAbout content={description} />
<RichTextDisplay content={description} />
</div>
) : null}
<DataDisclaimer />
Expand Down
60 changes: 60 additions & 0 deletions app/components/explore/Category.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use client'

import { useMemo, useState } from 'react'
import { TokenPageWithData } from '../../../contentful/tokenPage'
import { MangoTokenData } from '../../types/mango'
import CategorySwitcher from './CategorySwitcher'
import TableViewToggle from './TableViewToggle'
import TokenTable from './TokenTable'
import { TokenCategoryPage } from '../../../contentful/tokenCategoryPage'
import RichTextDisplay from '../shared/RichTextDisplay'
import DataDisclaimer from './DataDisclaimer'
import { sortTokens } from './Explore'

const SECTION_WRAPPER_CLASSES = 'border-t border-th-bkg-3 pt-6 mt-12'

const Category = ({
categoryPageData,
tokensForCategory,
mangoTokensData,
}: {
categoryPageData: TokenCategoryPage
tokensForCategory: TokenPageWithData[]
mangoTokensData: MangoTokenData[]
}) => {
const [showTableView, setShowTableView] = useState(true)
const { category, description } = categoryPageData

const sortedTokens = useMemo(() => {
return sortTokens(tokensForCategory)
}, [tokensForCategory])

return (
<>
<div className="mb-10 flex flex-col lg:flex-row lg:items-center lg:justify-between">
<h1 className="text-4xl mb-4 lg:mb-0">{`Explore ${category}`}</h1>
<div className="flex space-x-2">
<CategorySwitcher tokens={tokensForCategory} />
<TableViewToggle
showTableView={showTableView}
setShowTableView={setShowTableView}
/>
</div>
</div>
<TokenTable
tokens={sortedTokens}
mangoTokensData={mangoTokensData}
showTableView={showTableView}
/>
{description ? (
<div className={SECTION_WRAPPER_CLASSES}>
<h2 className="mb-4 text-2xl">{`About ${category}`}</h2>
<RichTextDisplay content={description} />
</div>
) : null}
<DataDisclaimer />
</>
)
}

export default Category
74 changes: 74 additions & 0 deletions app/components/explore/CategorySwitcher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { useEffect, useMemo, useState } from 'react'
import Select from '../forms/Select'
import { TokenPageWithData } from '../../../contentful/tokenPage'
import { AppRouterInstance } from 'next/dist/shared/lib/app-router-context.shared-runtime'
import { useParams, useRouter } from 'next/navigation'
import { tagToSlug } from '../../utils'

const goToCategoryPage = (categorySlug: string, router: AppRouterInstance) => {
router.push(`/explore/${categorySlug}`)
}

const CategorySwitcher = ({ tokens }: { tokens: TokenPageWithData[] }) => {
const router = useRouter()
const params = useParams()
const { category } = params
const [selectedCategory, setSelectedCategory] = useState('All')

const categories = useMemo(() => {
if (!tokens?.length) return []
const categories: string[] = []
for (const token of tokens) {
for (const tag of token.tags) {
if (!categories.includes(tag)) {
categories.push(tag)
}
}
}
return categories.sort((a, b) => a.localeCompare(b))
}, [tokens])

useEffect(() => {
if (category && categories.length) {
const categoryParts = category.toString().toLowerCase().split('-')

const matchedCategory = categories.find((cat) => {
const catLower = cat.toLowerCase()
return categoryParts.every((part) => catLower.includes(part))
})

if (matchedCategory) {
setSelectedCategory(matchedCategory)
}
}
}, [categories, category])

const handleSetCategory = (cat: string) => {
setSelectedCategory(cat)
if (cat === 'All') {
router.push('/explore', { shallow: true })
} else {
const slug = tagToSlug(cat)
goToCategoryPage(slug, router)
}
}

return (
<Select
value={selectedCategory}
onChange={(cat) => handleSetCategory(cat)}
className="w-1/2 lg:w-44 h-10 text-left whitespace-nowrap"
>
<>
<Select.Option value="All">All</Select.Option>
{categories.map((cat) => (
<Select.Option key={cat} value={cat}>
{cat}
</Select.Option>
))}
</>
</Select>
)
}

export default CategorySwitcher
Loading