55 lines
1.8 KiB
TypeScript

'use client';
import { fetchLatestPrices } from '@/actions/prices.js';
import type { getLatestPrices } from '@/generated/prisma/sql.js';
import { deserialize } from '@/lib/superjson.js';
import { useEffect, useRef } from 'react';
import { toast } from 'sonner';
const PRICE_SPIKE_THRESHOLD_MWH = 100;
const CHECK_INTERVAL_MS = 60_000;
export function PriceAlertMonitor() {
const previousPricesRef = useRef<Map<string, number>>(new Map());
const initialLoadRef = useRef(true);
useEffect(() => {
async function checkForSpikes() {
const result = await fetchLatestPrices();
if (!result.ok) return;
const prices = deserialize<getLatestPrices.Result[]>(result.data);
const prevPrices = previousPricesRef.current;
for (const p of prices) {
const prevPrice = prevPrices.get(p.region_code);
if (!initialLoadRef.current) {
if (p.price_mwh >= PRICE_SPIKE_THRESHOLD_MWH) {
toast.error(`Price Spike: ${p.region_code}`, {
description: `${p.region_name} hit $${p.price_mwh.toFixed(2)}/MWh — above $${PRICE_SPIKE_THRESHOLD_MWH} threshold`,
duration: 8000,
});
} else if (prevPrice !== undefined && p.price_mwh > prevPrice * 1.15) {
toast.warning(`Price Jump: ${p.region_code}`, {
description: `${p.region_name} jumped to $${p.price_mwh.toFixed(2)}/MWh (+${(((p.price_mwh - prevPrice) / prevPrice) * 100).toFixed(1)}%)`,
duration: 6000,
});
}
}
prevPrices.set(p.region_code, p.price_mwh);
}
initialLoadRef.current = false;
}
void checkForSpikes();
const interval = setInterval(() => void checkForSpikes(), CHECK_INTERVAL_MS);
return () => clearInterval(interval);
}, []);
return null;
}