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
Build Images and Deploy / Update-PROD-Stack (push) Failing after 32s
This commit is contained in:
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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
@@ -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()))
|
||||
|
||||
Reference in New Issue
Block a user