fix: resolve camera settings click and subscription API errors
- Camera settings: Use UUID `id` instead of optional `deviceId` for camera selection - Camera settings: Only fetch history when deviceId exists - Camera settings: Fix location display for JSONB location object - Subscription: Safely handle empty JSON response when user has no subscription 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
95353ae2d7
commit
1c272140d9
@ -45,26 +45,29 @@ export default function CameraSettingsPage() {
|
||||
const handleSelectCamera = async (cameraId: string) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// 获取相机详细信息
|
||||
const camera = cameras.find(c => c.deviceId === cameraId);
|
||||
|
||||
// 使用 id 查找相机
|
||||
const camera = cameras.find(c => c.id === cameraId);
|
||||
if (camera) {
|
||||
setCameraDetails(camera);
|
||||
setSelectedCamera(cameraId);
|
||||
|
||||
// 获取历史数据
|
||||
const historyData = await cameraService.getCameraHistory(cameraId);
|
||||
const formattedHistory: CameraHistoryRecord[] = historyData.map(item => ({
|
||||
date: new Date(item.time).toLocaleString(),
|
||||
status: 'active' as const, // API doesn't provide status in history, defaulting to active
|
||||
temperature: item.temperature,
|
||||
coolerPower: item.coolerPower,
|
||||
gain: item.gain,
|
||||
exposureCount: undefined,
|
||||
uptime: undefined
|
||||
}));
|
||||
|
||||
setCameraHistory(formattedHistory);
|
||||
|
||||
// 只有当 deviceId 存在时才获取历史数据
|
||||
if (camera.deviceId) {
|
||||
const historyData = await cameraService.getCameraHistory(camera.deviceId);
|
||||
const formattedHistory: CameraHistoryRecord[] = historyData.map(item => ({
|
||||
date: new Date(item.time).toLocaleString(),
|
||||
status: 'active' as const, // API doesn't provide status in history, defaulting to active
|
||||
temperature: item.temperature,
|
||||
coolerPower: item.coolerPower,
|
||||
gain: item.gain,
|
||||
exposureCount: undefined,
|
||||
uptime: undefined
|
||||
}));
|
||||
setCameraHistory(formattedHistory);
|
||||
} else {
|
||||
setCameraHistory([]);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取相机详细信息失败:', error);
|
||||
@ -160,7 +163,7 @@ export default function CameraSettingsPage() {
|
||||
<div
|
||||
key={camera.id}
|
||||
className="bg-white dark:bg-gray-800 rounded-lg p-4 sm:p-5 border border-gray-200 dark:border-gray-700 cursor-pointer hover:shadow-lg transition-all duration-200 hover:scale-105 min-w-[280px] w-full"
|
||||
onClick={() => handleSelectCamera(camera.deviceId)}
|
||||
onClick={() => handleSelectCamera(camera.id)}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center min-w-0 flex-1">
|
||||
@ -175,7 +178,7 @@ export default function CameraSettingsPage() {
|
||||
<div className="space-y-2.5">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400 flex-shrink-0">位置:</span>
|
||||
<span className="text-sm font-medium text-gray-900 dark:text-white truncate ml-2">{camera.location}</span>
|
||||
<span className="text-sm font-medium text-gray-900 dark:text-white truncate ml-2">{camera.location?.site_name || camera.legacyLocation || '未知'}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm text-gray-500 dark:text-gray-400 flex-shrink-0">温度:</span>
|
||||
@ -233,7 +236,7 @@ export default function CameraSettingsPage() {
|
||||
<Monitor className="text-blue-500 mr-3" size={32} />
|
||||
<div>
|
||||
<h3 className="text-2xl font-bold text-gray-900 dark:text-white">{cameraDetails.name}</h3>
|
||||
<p className="text-gray-500 dark:text-gray-400">{cameraDetails.location}</p>
|
||||
<p className="text-gray-500 dark:text-gray-400">{cameraDetails.location?.site_name || cameraDetails.legacyLocation || '未知'}</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className={`px-3 py-2 rounded-full text-sm font-medium ${getStatusColor(cameraDetails.status)}`}>
|
||||
|
||||
@ -32,7 +32,7 @@ export interface CheckoutSessionResponse {
|
||||
|
||||
// New interfaces for the enhanced subscription API
|
||||
export interface SubscriptionPlan {
|
||||
id: number;
|
||||
id: string;
|
||||
planId: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
@ -49,9 +49,9 @@ export interface SubscriptionPlan {
|
||||
}
|
||||
|
||||
export interface UserSubscription {
|
||||
id: number;
|
||||
id: string;
|
||||
userProfileId: string;
|
||||
subscriptionPlanId: number;
|
||||
subscriptionPlanId: string;
|
||||
subscriptionPlan: SubscriptionPlan;
|
||||
stripeSubscriptionId?: string;
|
||||
status: 'active' | 'canceled' | 'past_due' | 'trialing' | 'incomplete';
|
||||
@ -213,7 +213,13 @@ export const subscriptionApi = {
|
||||
throw new Error(`Failed to fetch user subscription: ${response.statusText}`)
|
||||
}
|
||||
|
||||
return response.json()
|
||||
// Safely handle empty response body
|
||||
const text = await response.text()
|
||||
if (!text || text.trim() === '' || text === 'null') {
|
||||
return null
|
||||
}
|
||||
|
||||
return JSON.parse(text)
|
||||
} catch (error) {
|
||||
console.error('Error fetching user subscription:', error)
|
||||
return null
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user