'use client' import { useState, useRef, useEffect, useId } from 'react' import { subMonths, format } from 'lib/utils' import { useAIState } from 'ai/rsc' interface Stock { symbol: string price: number delta: number } function scaleLinear(domain: [number, number], range: [number, number]) { const [d0, d1] = domain const [r0, r1] = range return function scale(value: number): number { return r0 + ((value - d0) / (d1 - d0)) * (r1 - r0) } } function useResizeObserver( ref: React.RefObject ) { const [size, setSize] = useState({ width: 0, height: 0 }) useEffect(() => { if (!ref.current) return const observer = new ResizeObserver(([entry]) => { if (entry.borderBoxSize && entry.borderBoxSize.length > 0) { setSize({ width: entry.borderBoxSize[0].inlineSize, height: entry.borderBoxSize[0].blockSize }) } else { // Fallback for browsers that don't support borderBoxSize const { width, height } = entry.target.getBoundingClientRect() setSize({ width, height }) } }) observer.observe(ref.current) return () => observer.disconnect() }, [ref]) return size } export function Stock({ props: { symbol, price, delta } }: { props: Stock }) { const [aiState, setAIState] = useAIState() const id = useId() const [priceAtTime, setPriceAtTime] = useState({ time: '00:00', value: price.toFixed(2), x: 0 }) const [startHighlight, setStartHighlight] = useState(0) const [endHighlight, setEndHighlight] = useState(0) const chartRef = useRef(null) const { width = 0 } = useResizeObserver(chartRef) const xToDate = scaleLinear( [0, width], [subMonths(new Date(), 6).getTime(), new Date().getTime()] ) const xToValue = scaleLinear( [0, width], [price - price / 2, price + price / 2] ) useEffect(() => { if (startHighlight && endHighlight) { const message = { id, role: 'system' as const, content: `[User has highlighted dates between between ${format( new Date(xToDate(startHighlight)), 'd LLL' )} and ${format(new Date(xToDate(endHighlight)), 'd LLL, yyyy')}` } if (aiState.messages[aiState.messages.length - 1]?.id === id) { setAIState({ ...aiState, messages: [...aiState.messages.slice(0, -1), message] }) } else { setAIState({ ...aiState, messages: [...aiState.messages, message] }) } } }, [startHighlight, endHighlight]) return (
{`${delta > 0 ? '+' : ''}${((delta / price) * 100).toFixed(2)}% ${ delta > 0 ? '↑' : '↓' }`}
{symbol}
${price}
Closed: Feb 27, 4:59 PM EST
{ if (chartRef.current) { const { clientX } = event const { left } = chartRef.current.getBoundingClientRect() setStartHighlight(clientX - left) setEndHighlight(0) setPriceAtTime({ time: format(new Date(xToDate(clientX)), 'dd LLL yy'), value: xToValue(clientX).toFixed(2), x: clientX - left }) } }} onPointerUp={event => { if (chartRef.current) { const { clientX } = event const { left } = chartRef.current.getBoundingClientRect() setEndHighlight(clientX - left) } }} onPointerMove={event => { if (chartRef.current) { const { clientX } = event const { left } = chartRef.current.getBoundingClientRect() setPriceAtTime({ time: format(new Date(xToDate(clientX)), 'dd LLL yy'), value: xToValue(clientX).toFixed(2), x: clientX - left }) } }} onPointerLeave={() => { setPriceAtTime({ time: '00:00', value: price.toFixed(2), x: 0 }) }} ref={chartRef} > {priceAtTime.x > 0 ? (
${priceAtTime.value}
{priceAtTime.time}
) : null} {startHighlight ? (
) : null}
) }