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:
grabbit 2025-12-21 03:25:03 +08:00
parent 95353ae2d7
commit 1c272140d9
2 changed files with 33 additions and 24 deletions

View File

@ -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)}`}>

View File

@ -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