Files
ocean-cleanup/src/components/external/TideDisplay.tsx
Paul Bailey 311d3aa60b fix: handle invalid weather API key gracefully and fix TideDisplay crash
- Fix TideDisplay component crash (use extremes instead of tides)
- Handle 401 weather API errors gracefully (return null data with note)
- Update weather controller to return 200 with null values instead of 503
- Add helpful message about API key activation time
- Fix tide type capitalization (High/Low instead of high/low)
2025-11-16 23:27:10 +00:00

108 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Tide times display component
import { useEffect, useState } from 'react';
import { format } from 'date-fns';
import type { TideData } from '@/types';
import { externalDataService } from '@/services/externalDataService';
interface TideDisplayProps {
latitude: number;
longitude: number;
}
export function TideDisplay({ latitude, longitude }: TideDisplayProps) {
const [tideData, setTideData] = useState<TideData | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let mounted = true;
const fetchTides = async () => {
try {
setLoading(true);
setError(null);
const data = await externalDataService.getTides(latitude, longitude);
if (mounted) {
setTideData(data);
}
} catch (err) {
if (mounted) {
setError('Unable to load tide data');
}
} finally {
if (mounted) {
setLoading(false);
}
}
};
fetchTides();
return () => {
mounted = false;
};
}, [latitude, longitude]);
if (loading) {
return (
<div className="bg-white rounded-lg shadow p-4">
<div className="flex items-center space-x-2">
<div className="w-5 h-5 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
<span className="text-sm text-gray-600">Loading tide data...</span>
</div>
</div>
);
}
if (error || !tideData) {
return (
<div className="bg-white rounded-lg shadow p-4">
<p className="text-sm text-gray-500">Tide data unavailable</p>
</div>
);
}
return (
<div className="bg-white rounded-lg shadow p-4">
<h3 className="text-lg font-semibold mb-3 flex items-center">
<span className="mr-2">🌊</span>
Tide Times
</h3>
{tideData.nextTide && (
<div className="mb-4 p-3 bg-blue-50 rounded-lg">
<div className="text-sm text-gray-600 mb-1">Next Tide</div>
<div className="flex items-center justify-between">
<span className="font-semibold capitalize">{tideData.nextTide.type} Tide</span>
<span className="text-sm">{format(new Date(tideData.nextTide.time), 'HH:mm')}</span>
</div>
<div className="text-sm text-gray-600 mt-1">
Height: {tideData.nextTide.height.toFixed(2)}m
</div>
</div>
)}
<div className="space-y-2">
<div className="text-sm font-medium text-gray-700 mb-2">Today's Tides</div>
{tideData.extremes && tideData.extremes.slice(0, 4).map((tide, index) => (
<div
key={index}
className="flex items-center justify-between py-2 border-b border-gray-100 last:border-0"
>
<div className="flex items-center space-x-3">
<span className="text-lg">{tide.type === 'High' ? '⬆️' : '⬇️'}</span>
<div>
<div className="font-medium capitalize">{tide.type} Tide</div>
<div className="text-xs text-gray-500">
{format(new Date(tide.time), 'HH:mm')}
</div>
</div>
</div>
<div className="text-sm text-gray-600">{tide.height.toFixed(2)}m</div>
</div>
))}
</div>
</div>
);
}