Skip to content

Commit

Permalink
Merge pull request #20 from blockworks-foundation/news-links
Browse files Browse the repository at this point in the history
news links
  • Loading branch information
saml33 authored Jan 18, 2024
2 parents de9def1 + 80579b5 commit 3596e78
Show file tree
Hide file tree
Showing 9 changed files with 29,649 additions and 24 deletions.
41 changes: 37 additions & 4 deletions app/(pages)/explore/categories/[category]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { Metadata } from 'next'
import { draftMode } from 'next/headers'
import { notFound } from 'next/navigation'
import { fetchTokenPagesForCategory } from '../../../../../contentful/tokenPage'
import {
TokenPageWithData,
fetchTokenPagesForCategory,
} from '../../../../../contentful/tokenPage'
import {
fetchTokenCategoryPage,
fetchTokenCategoryPages,
Expand All @@ -11,6 +14,11 @@ import Category from '../../../../components/explore/Category'
import { MAX_CONTENT_WIDTH } from '../../../../utils/constants'
import RichTextDisplay from '../../../../components/rich-text/RichTextDisplay'
import DataDisclaimer from '../../../../components/explore/DataDisclaimer'
import {
NewsArticle,
fetchNewsArticles,
} from '../../../../../contentful/newsArticle'
import NewsArticleCard from '../../../../components/explore/NewsArticleCard'

interface TokenCategoryPageParams {
category: string
Expand Down Expand Up @@ -65,12 +73,23 @@ async function TokenCategoryPage({ params }: TokenCategoryPageProps) {

const { category, description } = tokenCategoryPageData

// fetch token pages from contentful where the entry contains the category (tag)
const tokensForCategory = await fetchTokenPagesForCategory({
category,
const newsArticlesPromise: Promise<NewsArticle[]> = fetchNewsArticles({
category: category,
preview: draftMode().isEnabled,
})

// fetch token pages from contentful where the entry contains the category (tag)
const tokensForCategoryPromise: Promise<TokenPageWithData[]> =
fetchTokenPagesForCategory({
category,
preview: draftMode().isEnabled,
})

const [newsArticles, tokensForCategory] = await Promise.all([
newsArticlesPromise,
tokensForCategoryPromise,
])

// fetch mango token data for each token page
const promises = tokensForCategory.map((token) =>
fetchMangoTokenData(token.mint),
Expand All @@ -85,6 +104,20 @@ async function TokenCategoryPage({ params }: TokenCategoryPageProps) {
tokensForCategory={tokensForCategory}
mangoTokensData={mangoTokensData}
/>
{newsArticles?.length ? (
<div className="border-t border-th-bkg-3 pt-10 md:pt-16">
<div
className={`px-6 lg:px-20 pb-10 md:pb-16 ${MAX_CONTENT_WIDTH} mx-auto`}
>
<h2 className="mb-4 text-2xl">News</h2>
<div className="grid grid-cols-3 gap-6">
{newsArticles.map((article) => (
<NewsArticleCard article={article} key={article.articleUrl} />
))}
</div>
</div>
</div>
) : null}
{description ? (
<div className="bg-th-bkg-2 py-10 md:py-16">
<div className={`px-6 lg:px-20 ${MAX_CONTENT_WIDTH} mx-auto`}>
Expand Down
29 changes: 27 additions & 2 deletions app/(pages)/explore/tokens/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ import TokenMangoStats from '../../../../components/explore/token-page/TokenMang
import DataDisclaimer from '../../../../components/explore/DataDisclaimer'
import InfoAndStats from '../../../../components/explore/token-page/InfoAndStats'
import { MAX_CONTENT_WIDTH } from '../../../../utils/constants'
import {
NewsArticle,
fetchNewsArticles,
} from '../../../../../contentful/newsArticle'
import NewsArticleCard from '../../../../components/explore/NewsArticleCard'

interface TokenPageParams {
slug: string
Expand Down Expand Up @@ -62,7 +67,7 @@ async function TokenPage({ params }: TokenPageProps) {
return notFound()
}

const { description, mint, tokenName } = tokenPageData
const { description, mint, tokenName, symbol } = tokenPageData

// get mango specific token data
const mangoTokenDataPromise: Promise<MangoTokenData> =
Expand All @@ -72,10 +77,16 @@ async function TokenPage({ params }: TokenPageProps) {
const mangoMarketsDataPromise: Promise<MangoMarketsData> =
fetchMangoMarketData()

const newsArticlesPromise: Promise<NewsArticle[]> = fetchNewsArticles({
category: symbol,
preview: draftMode().isEnabled,
})

// Wait for the promises to resolve
const [mangoTokenData, mangoMarketsData] = await Promise.all([
const [mangoTokenData, mangoMarketsData, newsArticles] = await Promise.all([
mangoTokenDataPromise,
mangoMarketsDataPromise,
newsArticlesPromise,
])

return (
Expand All @@ -96,6 +107,20 @@ async function TokenPage({ params }: TokenPageProps) {
/>
</div>
</div>
{newsArticles?.length ? (
<div className="border-t border-th-bkg-3 pt-10 md:pt-16">
<div
className={`px-6 lg:px-20 pb-10 md:pb-16 ${MAX_CONTENT_WIDTH} mx-auto`}
>
<h2 className="mb-4 text-2xl">News</h2>
<div className="grid grid-cols-3 gap-6">
{newsArticles.map((article) => (
<NewsArticleCard article={article} key={article.articleUrl} />
))}
</div>
</div>
</div>
) : null}
{description ? (
<div className="bg-th-bkg-2 py-10 md:py-16">
<div className={`px-6 lg:px-20 ${MAX_CONTENT_WIDTH} mx-auto`}>
Expand Down
22 changes: 22 additions & 0 deletions app/components/explore/NewsArticleCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import dayjs from 'dayjs'
import { NewsArticle } from '../../../contentful/newsArticle'

const NewsArticleCard = ({ article }: { article: NewsArticle }) => {
const { title, articleUrl, imageUrl, sourceSiteName, datePublished } = article
return (
<a
className="col-span-3 md:col-span-1 md:hover:opacity-80"
href={articleUrl}
key={articleUrl}
rel="noopener noreferrer"
target="_blank"
>
<img src={imageUrl} className="w-48 h-auto" />
<h4 className="mt-4">{title}</h4>
<p className="mt-2 text-sm text-th-fgd-2">{sourceSiteName}</p>
<p className="text-sm">{dayjs(datePublished).format('D MMM YYYY')}</p>
</a>
)
}

export default NewsArticleCard
6 changes: 1 addition & 5 deletions app/components/rich-text/RichText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,7 @@ function RichText({ document, currentPrice }: RichTextProps) {
[BLOCKS.HR]: () => <Spacer />,
[INLINES.EMBEDDED_ENTRY]: (node) => {
if (node.data.target.sys.contentType.sys.id === 'tokenCallToAction') {
return (
<div>
<TokenCallToAction data={node.data.target.fields} />
</div>
)
return <TokenCallToAction data={node.data.target.fields} />
}
if (node.data.target.sys.contentType.sys.id === 'inlineTextPrice') {
const priceType = node.data.target.fields.priceType
Expand Down
24 changes: 12 additions & 12 deletions app/components/rich-text/TokenCallToAction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ const TokenCallToAction = ({ data }: { data: CtaData }) => {
? `/icons/tokens/${bankName.toLowerCase()}.svg`
: ''
return (
<div className="bg-gradient-to-br from-th-bkg-3 to-th-bkg-4 p-4 md:px-6 rounded-lg flex flex-col md:flex-row md:items-center md:justify-between my-4">
<div className="flex items-center space-x-3">
<span className="bg-th-bkg-1 border border-th-bkg-4 p-4 md:px-6 rounded-lg flex flex-col md:flex-row md:items-center md:justify-between my-4">
<span className="flex items-center space-x-3">
{logoUrl ? (
<Image
className="flex-shrink-0"
Expand All @@ -32,28 +32,28 @@ const TokenCallToAction = ({ data }: { data: CtaData }) => {
) : (
<QuestionMarkCircleIcon className="h-10 w-10 text-th-fgd-4 flex-shrink-0" />
)}
<div>
<p className="mb-1 text-th-fgd-2 font-display">{title}</p>
<p className="text-sm text-th-fgd-2">{description}</p>
</div>
</div>
<div className="flex space-x-2 md:pl-4 pt-4 md:pt-0">
<span>
<span className="text-th-fgd-2 text-base block font-display">
{title}
</span>
<span className="text-sm block text-th-fgd-2">{description}</span>
</span>
</span>
<span className="flex space-x-2 md:pl-4 pt-4 md:pt-0">
<ButtonLink
linkText={`Swap ${bankName}`}
path={`https://app.mango.markets/swap?in=USDC&out=${bankName}`}
// secondary
size="small"
target="_blank"
/>
<ButtonLink
linkText={`Trade ${bankName}`}
path={`https://app.mango.markets/trade?name=${spotMarketName}`}
// secondary
size="small"
target="_blank"
/>
</div>
</div>
</span>
</span>
)
}

Expand Down
60 changes: 60 additions & 0 deletions contentful/newsArticle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { TypeNewsArticleSkeleton } from './types'
import { Entry } from 'contentful'
import contentfulClient from './contentfulClient'

type NewsArticleEntry = Entry<TypeNewsArticleSkeleton, undefined, string>

// Our simplified version of a NewsArticle.
// We don't need all the data that Contentful gives us.
export interface NewsArticle {
title: string
articleUrl: string
imageUrl: string | undefined
sourceSiteName: string
datePublished: string
categories: string[]
}

// A function to transform a Contentful blog post
// into our own NewsArticle object.
export function parseContentfulNewsArticle(
newsArticleEntry?: NewsArticleEntry,
): NewsArticle | null {
if (!newsArticleEntry) {
return null
}

return {
title: newsArticleEntry.fields.title || '',
articleUrl: newsArticleEntry.fields.articleUrl,
imageUrl: newsArticleEntry.fields.imageUrl || '',
sourceSiteName: newsArticleEntry.fields.sourceSiteName,
datePublished: newsArticleEntry.fields.datePublished,
categories: newsArticleEntry.fields.categories,
}
}

// A function to fetch all blog posts.
// Optionally uses the Contentful content preview.
interface FetchNewsArticlesOptions {
preview: boolean
category: any
}
export async function fetchNewsArticles({
preview,
category,
}: FetchNewsArticlesOptions): Promise<NewsArticle[]> {
const contentful = contentfulClient({ preview })

const newsArticlesResult =
await contentful.getEntries<TypeNewsArticleSkeleton>({
content_type: 'newsArticle',
include: 2,
'fields.categories[in]': category,
})

return newsArticlesResult.items.map(
(newsArticleEntry) =>
parseContentfulNewsArticle(newsArticleEntry) as NewsArticle,
)
}
86 changes: 86 additions & 0 deletions contentful/types/TypeNewsArticle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import type {
ChainModifiers,
Entry,
EntryFieldTypes,
EntrySkeletonType,
LocaleCode,
} from 'contentful'

export interface TypeNewsArticleFields {
title: EntryFieldTypes.Symbol
articleUrl: EntryFieldTypes.Symbol
imageUrl?: EntryFieldTypes.Symbol
sourceSiteName: EntryFieldTypes.Symbol
datePublished: EntryFieldTypes.Symbol
categories: EntryFieldTypes.Array<
EntryFieldTypes.Symbol<
| 'AI'
| 'ALL'
| 'AMM'
| 'BONK'
| 'Bridged (Portal)'
| 'CHAI'
| 'CORN'
| 'CROWN'
| 'Communication'
| 'DUAL'
| 'DeFi'
| 'DePIN'
| 'Dog Coin'
| 'Domains'
| 'ETH (Portal)'
| 'Exchange'
| 'GUAC'
| 'Gambling'
| 'Gaming'
| 'Governance'
| 'HNT'
| 'Infrastructure'
| 'JLP'
| 'JTO'
| 'JitoSOL'
| 'KIN'
| 'LDO'
| 'Layer 1'
| 'Liquid Staking'
| 'MNGO'
| 'Meme'
| 'NEON'
| 'NOS'
| 'OPOS'
| 'ORCA'
| 'Options'
| 'Oracle'
| 'PYTH'
| 'Payments'
| 'Perps'
| 'RAY'
| 'RENDER'
| 'RLB'
| 'SAMO'
| 'SLCL'
| 'SOL'
| 'Social'
| 'Stablecoin'
| 'TBTC'
| 'Tokenized Asset'
| 'USDC'
| 'USDH'
| 'USDT'
| 'WIF'
| 'Yield Bearing'
| 'bSOL'
| 'mSOL'
| 'wBTC (Portal)'
>
>
}

export type TypeNewsArticleSkeleton = EntrySkeletonType<
TypeNewsArticleFields,
'newsArticle'
>
export type TypeNewsArticle<
Modifiers extends ChainModifiers,
Locales extends LocaleCode,
> = Entry<TypeNewsArticleSkeleton, Modifiers, Locales>
5 changes: 5 additions & 0 deletions contentful/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ export type {
TypeLearnPostFields,
TypeLearnPostSkeleton,
} from './TypeLearnPost'
export type {
TypeNewsArticle,
TypeNewsArticleFields,
TypeNewsArticleSkeleton,
} from './TypeNewsArticle'
export type { TypeToken, TypeTokenFields, TypeTokenSkeleton } from './TypeToken'
export type {
TypeTokenCallToAction,
Expand Down
29,400 changes: 29,399 additions & 1 deletion tsconfig.tsbuildinfo

Large diffs are not rendered by default.

1 comment on commit 3596e78

@vercel
Copy link

@vercel vercel bot commented on 3596e78 Jan 18, 2024

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.