Jaspers Hub

biseroff / benzinga

pluginReviewed★ 0↓ 2

Benzinga Market Data: Fetches Benzinga news, analyst ratings, earnings, guidance, dividends, splits, IPO, and economic calendars via API, with a news list view.

2.8 KB
1import { useBridge, useData, usePublish, type PanelRef } from '@jaspers-ai/sdk'
2import './styles.css'
3
4interface Story {
5 id: number
6 created: string
7 title: string
8 teaser: string
9 url: string
10 tickers: string
11 [field: string]: unknown
12}
13
14interface State {
15 tickers?: string[]
16 channels?: string[]
17 pageSize?: number
18}
19
20export function NewsView({ panel }: { panel: PanelRef }) {
21 const state = useData(`workspaces/${panel.workspaceId}/panels/${panel.id}/state`) as State | undefined
22 if (state === undefined) return <p className="bz-note">Loading…</p>
23 return <List panel={panel} tickers={state.tickers ?? []} channels={state.channels ?? []} pageSize={state.pageSize ?? 30} />
24}
25
26function when(iso: string): string {
27 const date = new Date(iso)
28 if (Number.isNaN(date.getTime())) return iso
29 const today = new Date().toDateString() === date.toDateString()
30 return today
31 ? date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
32 : date.toLocaleDateString([], { month: 'short', day: 'numeric' }) + ' ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
33}
34
35function List({ panel, tickers, channels, pageSize }: { panel: PanelRef; tickers: string[]; channels: string[]; pageSize: number }) {
36 const args: Record<string, unknown> = { pageSize, displayOutput: 'abstract' }
37 if (tickers.length) args.tickers = tickers.join(',')
38 if (channels.length) args.channels = channels.join(',')
39 const { data, error } = useData('benzinga/news', args)
40 const bridge = useBridge()
41 const rows = (data ?? []) as Story[]
42 usePublish(panel, {
43 tickers,
44 count: rows.length,
45 headlines: rows.slice(0, 10).map((r) => ({ created: r.created, title: r.title })),
46 })
47
48 const heading = `Benzinga news${tickers.length ? ` · ${tickers.join(', ')}` : ''}`
49 return (
50 <div className="bz">
51 <h3 className="bz-title">{heading}</h3>
52 {error ? (
53 <p className="bz-note bz-error">{error}</p>
54 ) : !data ? (
55 <p className="bz-note">Loading…</p>
56 ) : rows.length === 0 ? (
57 <p className="bz-note">No stories in the last 7 days.</p>
58 ) : (
59 <ul className="bz-list">
60 {rows.map((row) => (
61 <li key={row.id} className="bz-item">
62 <div className="bz-meta">
63 <span className="bz-time">{when(row.created)}</span>
64 {row.tickers && <span className="bz-tickers">{row.tickers.split(',').slice(0, 6).join(' ')}</span>}
65 </div>
66 <a
67 className="bz-headline"
68 href={row.url}
69 onClick={(event) => {
70 event.preventDefault()
71 if (row.url) bridge.openLink(row.url)
72 }}
73 >
74 {row.title}
75 </a>
76 {row.teaser && <p className="bz-teaser">{row.teaser}</p>}
77 </li>
78 ))}
79 </ul>
80 )}
81 </div>
82 )
83}