From 40a10340003e8b21df0d569dec296420d8880674 Mon Sep 17 00:00:00 2001 From: Mike Johnston Date: Thu, 19 Mar 2026 15:00:46 -0400 Subject: [PATCH] refactor: enhance stock change display and improve hashtag card status logic and timeline fetch logic --- src/app/stocks/page.tsx | 12 +++++------- src/components/HashtagCard.tsx | 14 ++++++++------ src/lib/mastodon.ts | 10 +++++----- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/app/stocks/page.tsx b/src/app/stocks/page.tsx index 73a3cab..aa9d04b 100644 --- a/src/app/stocks/page.tsx +++ b/src/app/stocks/page.tsx @@ -1,5 +1,5 @@ import { prisma } from '@/lib/prisma' -import { formatCurrency } from '@/lib/utils' +import { formatCurrency, pnlColor } from '@/lib/utils' import Link from 'next/link' import { ArrowUp, ArrowDown, ArrowUpDown, BarChart2, Building2 } from 'lucide-react' import { formatDistanceToNow } from 'date-fns' @@ -265,8 +265,6 @@ export default async function StocksPage({ searchParams }: PageProps) { const prev = stock.previousPrice const change = prev != null ? stock.currentPrice - prev : null const changePct = prev != null && prev > 0 ? ((stock.currentPrice - prev) / prev) * 100 : null - const up = change == null ? null : change >= 0 - return (
— ) : (
-

- {up ? '+' : ''}{formatCurrency(change)} +

+ {change > 0 ? '+' : ''}{formatCurrency(change)}

-

- {up ? '+' : ''}{changePct!.toFixed(2)}% +

+ {changePct! > 0 ? '+' : ''}{changePct!.toFixed(2)}%

)} diff --git a/src/components/HashtagCard.tsx b/src/components/HashtagCard.tsx index b2297ab..7b297f6 100644 --- a/src/components/HashtagCard.tsx +++ b/src/components/HashtagCard.tsx @@ -1,5 +1,5 @@ import Link from 'next/link' -import { TrendingUp, TrendingDown } from 'lucide-react' +import { TrendingUp, TrendingDown, Minus } from 'lucide-react' import { formatCurrency } from '@/lib/utils' interface Props { @@ -16,7 +16,7 @@ export function HashtagCard({ tag, displayTag, currentPrice, previousPrice, post ? ((currentPrice - previousPrice) / previousPrice) * 100 : null - const up = pctChange === null ? null : pctChange >= 0 + const up = pctChange === null ? null : pctChange > 0 ? 'up' : pctChange < 0 ? 'down' : 'flat' return ( {formatCurrency(currentPrice)}

{pctChange !== null && (
- {up ? ( + {up === 'up' ? ( - ) : ( + ) : up === 'down' ? ( + ) : ( + )} - {up ? '+' : ''} + {up === 'up' ? '+' : ''} {pctChange.toFixed(1)}%
)} diff --git a/src/lib/mastodon.ts b/src/lib/mastodon.ts index 43d0d0b..10069f3 100644 --- a/src/lib/mastodon.ts +++ b/src/lib/mastodon.ts @@ -31,12 +31,11 @@ function extractTagsFromHtml(html: string): string[] { return results } -async function fetchPage(tag: string, maxId?: string): Promise { +async function fetchPage(tag: string, maxId?: string, postLimit = 20): Promise { const instance = process.env.MASTODON_INSTANCE if (!instance) throw new Error('MASTODON_INSTANCE is not configured') - let url = `${instance}/api/v1/timelines/tag/${encodeURIComponent(tag)}` - // ?limit=50 was here but it seemed too aggressive. Default is 20 which feels more balanced for pricing purposes and reduces risk of hitting rate limits on very active tags. + let url = `${instance}/api/v1/timelines/tag/${encodeURIComponent(tag)}?limit=${postLimit}` if (maxId) url += `&max_id=${maxId}` const headers: HeadersInit = { Accept: 'application/json' } @@ -83,6 +82,7 @@ export async function getPostsData( tag: string, ): Promise<{ postsPerHour: number; relatedTags: string[]; displayTag?: string }> { const maxPages = parseInt(process.env.MAX_PAGES_PER_HASHTAG ?? '5', 10) + const postLimit = Math.min(parseInt(process.env.MASTODON_POST_LIMIT ?? '20', 10), 40) const ONE_HOUR_MS = 60 * 60 * 1000 const now = Date.now() const cutoff = now - ONE_HOUR_MS @@ -91,13 +91,13 @@ export async function getPostsData( let maxId: string | undefined for (let page = 0; page < maxPages; page++) { - const { posts, nextMaxId } = await fetchPage(tag, maxId) + const { posts, nextMaxId } = await fetchPage(tag, maxId, postLimit) if (posts.length === 0) break allPosts = [...allPosts, ...posts] // End of timeline or no more pages - if (posts.length < 40 || !nextMaxId) break + if (posts.length < postLimit || !nextMaxId) break // If the oldest post in this batch is already beyond 1 hour, we have a full window const oldestInBatch = Math.min(...posts.map((p) => new Date(p.created_at).getTime()))