refactor: enhance stock change display and improve hashtag card status logic and timeline fetch logic
Build Images and Deploy / Update-PROD-Stack (push) Failing after 32s

This commit is contained in:
2026-03-19 15:00:46 -04:00
parent fdb234641f
commit 40a1034000
3 changed files with 18 additions and 18 deletions
+5 -7
View File
@@ -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 (
<div
key={stock.id}
@@ -296,11 +294,11 @@ export default async function StocksPage({ searchParams }: PageProps) {
<span className="text-slate-600 text-xs"></span>
) : (
<div>
<p className={`text-sm ${up ? 'text-emerald-400' : 'text-red-400'}`}>
{up ? '+' : ''}{formatCurrency(change)}
<p className={`text-sm ${pnlColor(change)}`}>
{change > 0 ? '+' : ''}{formatCurrency(change)}
</p>
<p className={`text-xs ${up ? 'text-emerald-400' : 'text-red-400'}`}>
{up ? '+' : ''}{changePct!.toFixed(2)}%
<p className={`text-xs ${pnlColor(changePct)}`}>
{changePct! > 0 ? '+' : ''}{changePct!.toFixed(2)}%
</p>
</div>
)}
+8 -6
View File
@@ -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 (
<Link
@@ -36,14 +36,16 @@ export function HashtagCard({ tag, displayTag, currentPrice, previousPrice, post
<p className="font-bold text-sm">{formatCurrency(currentPrice)}</p>
{pctChange !== null && (
<div
className={`flex items-center justify-end gap-0.5 text-xs mt-0.5 ${up ? 'text-emerald-400' : 'text-red-400'}`}
className={`flex items-center justify-end gap-0.5 text-xs mt-0.5 ${up === 'up' ? 'text-emerald-400' : up === 'down' ? 'text-red-400' : 'text-slate-400'}`}
>
{up ? (
{up === 'up' ? (
<TrendingUp className="h-3 w-3" />
) : (
) : up === 'down' ? (
<TrendingDown className="h-3 w-3" />
) : (
<Minus className="h-3 w-3" />
)}
{up ? '+' : ''}
{up === 'up' ? '+' : ''}
{pctChange.toFixed(1)}%
</div>
)}
+5 -5
View File
@@ -31,12 +31,11 @@ function extractTagsFromHtml(html: string): string[] {
return results
}
async function fetchPage(tag: string, maxId?: string): Promise<TimelineResult> {
async function fetchPage(tag: string, maxId?: string, postLimit = 20): Promise<TimelineResult> {
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()))