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 { prisma } from '@/lib/prisma'
|
||||||
import { formatCurrency } from '@/lib/utils'
|
import { formatCurrency, pnlColor } from '@/lib/utils'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { ArrowUp, ArrowDown, ArrowUpDown, BarChart2, Building2 } from 'lucide-react'
|
import { ArrowUp, ArrowDown, ArrowUpDown, BarChart2, Building2 } from 'lucide-react'
|
||||||
import { formatDistanceToNow } from 'date-fns'
|
import { formatDistanceToNow } from 'date-fns'
|
||||||
@@ -265,8 +265,6 @@ export default async function StocksPage({ searchParams }: PageProps) {
|
|||||||
const prev = stock.previousPrice
|
const prev = stock.previousPrice
|
||||||
const change = prev != null ? stock.currentPrice - prev : null
|
const change = prev != null ? stock.currentPrice - prev : null
|
||||||
const changePct = prev != null && prev > 0 ? ((stock.currentPrice - prev) / prev) * 100 : null
|
const changePct = prev != null && prev > 0 ? ((stock.currentPrice - prev) / prev) * 100 : null
|
||||||
const up = change == null ? null : change >= 0
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={stock.id}
|
key={stock.id}
|
||||||
@@ -296,11 +294,11 @@ export default async function StocksPage({ searchParams }: PageProps) {
|
|||||||
<span className="text-slate-600 text-xs">—</span>
|
<span className="text-slate-600 text-xs">—</span>
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
<p className={`text-sm ${up ? 'text-emerald-400' : 'text-red-400'}`}>
|
<p className={`text-sm ${pnlColor(change)}`}>
|
||||||
{up ? '+' : ''}{formatCurrency(change)}
|
{change > 0 ? '+' : ''}{formatCurrency(change)}
|
||||||
</p>
|
</p>
|
||||||
<p className={`text-xs ${up ? 'text-emerald-400' : 'text-red-400'}`}>
|
<p className={`text-xs ${pnlColor(changePct)}`}>
|
||||||
{up ? '+' : ''}{changePct!.toFixed(2)}%
|
{changePct! > 0 ? '+' : ''}{changePct!.toFixed(2)}%
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { TrendingUp, TrendingDown } from 'lucide-react'
|
import { TrendingUp, TrendingDown, Minus } from 'lucide-react'
|
||||||
import { formatCurrency } from '@/lib/utils'
|
import { formatCurrency } from '@/lib/utils'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -16,7 +16,7 @@ export function HashtagCard({ tag, displayTag, currentPrice, previousPrice, post
|
|||||||
? ((currentPrice - previousPrice) / previousPrice) * 100
|
? ((currentPrice - previousPrice) / previousPrice) * 100
|
||||||
: null
|
: null
|
||||||
|
|
||||||
const up = pctChange === null ? null : pctChange >= 0
|
const up = pctChange === null ? null : pctChange > 0 ? 'up' : pctChange < 0 ? 'down' : 'flat'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
@@ -36,14 +36,16 @@ export function HashtagCard({ tag, displayTag, currentPrice, previousPrice, post
|
|||||||
<p className="font-bold text-sm">{formatCurrency(currentPrice)}</p>
|
<p className="font-bold text-sm">{formatCurrency(currentPrice)}</p>
|
||||||
{pctChange !== null && (
|
{pctChange !== null && (
|
||||||
<div
|
<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" />
|
<TrendingUp className="h-3 w-3" />
|
||||||
) : (
|
) : up === 'down' ? (
|
||||||
<TrendingDown className="h-3 w-3" />
|
<TrendingDown className="h-3 w-3" />
|
||||||
|
) : (
|
||||||
|
<Minus className="h-3 w-3" />
|
||||||
)}
|
)}
|
||||||
{up ? '+' : ''}
|
{up === 'up' ? '+' : ''}
|
||||||
{pctChange.toFixed(1)}%
|
{pctChange.toFixed(1)}%
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
+5
-5
@@ -31,12 +31,11 @@ function extractTagsFromHtml(html: string): string[] {
|
|||||||
return results
|
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
|
const instance = process.env.MASTODON_INSTANCE
|
||||||
if (!instance) throw new Error('MASTODON_INSTANCE is not configured')
|
if (!instance) throw new Error('MASTODON_INSTANCE is not configured')
|
||||||
|
|
||||||
let url = `${instance}/api/v1/timelines/tag/${encodeURIComponent(tag)}`
|
let url = `${instance}/api/v1/timelines/tag/${encodeURIComponent(tag)}?limit=${postLimit}`
|
||||||
// ?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.
|
|
||||||
if (maxId) url += `&max_id=${maxId}`
|
if (maxId) url += `&max_id=${maxId}`
|
||||||
|
|
||||||
const headers: HeadersInit = { Accept: 'application/json' }
|
const headers: HeadersInit = { Accept: 'application/json' }
|
||||||
@@ -83,6 +82,7 @@ export async function getPostsData(
|
|||||||
tag: string,
|
tag: string,
|
||||||
): Promise<{ postsPerHour: number; relatedTags: string[]; displayTag?: string }> {
|
): Promise<{ postsPerHour: number; relatedTags: string[]; displayTag?: string }> {
|
||||||
const maxPages = parseInt(process.env.MAX_PAGES_PER_HASHTAG ?? '5', 10)
|
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 ONE_HOUR_MS = 60 * 60 * 1000
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const cutoff = now - ONE_HOUR_MS
|
const cutoff = now - ONE_HOUR_MS
|
||||||
@@ -91,13 +91,13 @@ export async function getPostsData(
|
|||||||
let maxId: string | undefined
|
let maxId: string | undefined
|
||||||
|
|
||||||
for (let page = 0; page < maxPages; page++) {
|
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
|
if (posts.length === 0) break
|
||||||
allPosts = [...allPosts, ...posts]
|
allPosts = [...allPosts, ...posts]
|
||||||
|
|
||||||
// End of timeline or no more pages
|
// 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
|
// 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()))
|
const oldestInBatch = Math.min(...posts.map((p) => new Date(p.created_at).getTime()))
|
||||||
|
|||||||
Reference in New Issue
Block a user