fix bug and version check
Some checks failed
Deploy MyApp on Same Server / build-and-deploy (push) Failing after 1s
Some checks failed
Deploy MyApp on Same Server / build-and-deploy (push) Failing after 1s
This commit is contained in:
@@ -482,23 +482,43 @@ function DailyReportContent() {
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="bg-white rounded-2xl shadow-md border border-gray-200 overflow-hidden">
|
||||
<div className="flex border-b border-gray-200 overflow-x-auto">
|
||||
{TABS.map(tab => (
|
||||
<button
|
||||
key={tab.value}
|
||||
onClick={() => setActiveTab(tab.value)}
|
||||
className={`flex-1 min-w-[120px] px-6 py-4 text-sm font-medium transition-all duration-200 whitespace-nowrap ${
|
||||
activeTab === tab.value
|
||||
? 'text-indigo-600 border-b-2 border-indigo-600 bg-indigo-50/50'
|
||||
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
{/* Segmented Control for Mobile */}
|
||||
<div className="p-3 md:p-6 md:pb-0">
|
||||
<div className="bg-gray-100 rounded-xl p-1 flex md:hidden">
|
||||
{TABS.map(tab => (
|
||||
<button
|
||||
key={tab.value}
|
||||
onClick={() => setActiveTab(tab.value)}
|
||||
className={`flex-1 px-2 py-2.5 text-xs font-medium rounded-lg transition-all duration-200 ${
|
||||
activeTab === tab.value
|
||||
? 'bg-white text-indigo-600 shadow-sm'
|
||||
: 'text-gray-600 hover:text-gray-900'
|
||||
}`}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Desktop Tabs */}
|
||||
<div className="hidden md:flex border-b border-gray-200 -mx-6 -mt-6 mb-6">
|
||||
{TABS.map(tab => (
|
||||
<button
|
||||
key={tab.value}
|
||||
onClick={() => setActiveTab(tab.value)}
|
||||
className={`flex-1 px-6 py-4 text-sm font-medium transition-all duration-200 whitespace-nowrap ${
|
||||
activeTab === tab.value
|
||||
? 'text-indigo-600 border-b-2 border-indigo-600 bg-indigo-50/50'
|
||||
: 'text-gray-600 hover:text-gray-900 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
{tab.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-6">
|
||||
<div className="p-4 md:p-6 md:pt-0">
|
||||
{/* Summary Tab */}
|
||||
{activeTab === 'summary' && (
|
||||
<SummaryTab
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Metadata } from 'next'
|
||||
import './globals.css'
|
||||
import ServiceWorkerRegistration from '@/components/ServiceWorkerRegistration'
|
||||
import UpdateNotification from '@/components/UpdateNotification'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'GreenHome',
|
||||
@@ -35,6 +36,7 @@ export default function RootLayout({ children }: Readonly<{ children: React.Reac
|
||||
</head>
|
||||
<body className='persian-number'>
|
||||
<ServiceWorkerRegistration />
|
||||
<UpdateNotification />
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -8,26 +8,51 @@ export default function ServiceWorkerRegistration() {
|
||||
typeof window !== 'undefined' &&
|
||||
'serviceWorker' in navigator
|
||||
) {
|
||||
let registration: ServiceWorkerRegistration | null = null;
|
||||
let checkInterval: NodeJS.Timeout | null = null;
|
||||
|
||||
const handleMessage = (event: MessageEvent) => {
|
||||
if (event.data && event.data.type === 'SW_UPDATED') {
|
||||
// Service worker has updated, reload the page
|
||||
window.location.reload();
|
||||
}
|
||||
};
|
||||
|
||||
const handleVisibilityChange = () => {
|
||||
if (!document.hidden && registration) {
|
||||
registration.update();
|
||||
}
|
||||
};
|
||||
|
||||
const handleFocus = () => {
|
||||
if (registration) {
|
||||
registration.update();
|
||||
}
|
||||
};
|
||||
|
||||
const registerServiceWorker = async () => {
|
||||
try {
|
||||
const registration = await navigator.serviceWorker.register('/sw.js', {
|
||||
registration = await navigator.serviceWorker.register('/sw.js', {
|
||||
scope: '/',
|
||||
});
|
||||
|
||||
console.log('Service Worker registered successfully:', registration.scope);
|
||||
|
||||
// Handle updates
|
||||
registration.addEventListener('updatefound', () => {
|
||||
const newWorker = registration.installing;
|
||||
if (newWorker) {
|
||||
newWorker.addEventListener('statechange', () => {
|
||||
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
|
||||
// New service worker available
|
||||
console.log('New service worker available');
|
||||
}
|
||||
});
|
||||
// Listen for messages from service worker
|
||||
navigator.serviceWorker.addEventListener('message', handleMessage);
|
||||
|
||||
// Check for updates periodically
|
||||
checkInterval = setInterval(() => {
|
||||
if (registration) {
|
||||
registration.update();
|
||||
}
|
||||
});
|
||||
}, 60000); // Check every minute
|
||||
|
||||
// Check for updates when page becomes visible
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||
|
||||
// Check for updates on page focus
|
||||
window.addEventListener('focus', handleFocus);
|
||||
} catch (error) {
|
||||
console.error('Service Worker registration failed:', error);
|
||||
}
|
||||
@@ -39,6 +64,16 @@ export default function ServiceWorkerRegistration() {
|
||||
} else {
|
||||
window.addEventListener('load', registerServiceWorker);
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
return () => {
|
||||
if (checkInterval) {
|
||||
clearInterval(checkInterval);
|
||||
}
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||
window.removeEventListener('focus', handleFocus);
|
||||
navigator.serviceWorker.removeEventListener('message', handleMessage);
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
||||
140
src/components/UpdateNotification.tsx
Normal file
140
src/components/UpdateNotification.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { RefreshCw, X } from 'lucide-react';
|
||||
|
||||
export default function UpdateNotification() {
|
||||
const [showUpdate, setShowUpdate] = useState(false);
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined' || !('serviceWorker' in navigator)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let registration: ServiceWorkerRegistration | null = null;
|
||||
|
||||
const checkForUpdates = async () => {
|
||||
try {
|
||||
registration = await navigator.serviceWorker.ready;
|
||||
|
||||
// Listen for service worker updates
|
||||
registration.addEventListener('updatefound', () => {
|
||||
const newWorker = registration?.installing;
|
||||
if (newWorker) {
|
||||
newWorker.addEventListener('statechange', () => {
|
||||
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
|
||||
// New service worker available
|
||||
setShowUpdate(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Check for updates every 60 seconds
|
||||
const checkInterval = setInterval(async () => {
|
||||
try {
|
||||
await registration?.update();
|
||||
} catch (error) {
|
||||
console.error('Error checking for updates:', error);
|
||||
}
|
||||
}, 60000);
|
||||
|
||||
// Also check on page visibility change
|
||||
const handleVisibilityChange = () => {
|
||||
if (!document.hidden && registration) {
|
||||
registration.update();
|
||||
}
|
||||
};
|
||||
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||
|
||||
// Check on page focus
|
||||
const handleFocus = () => {
|
||||
if (registration) {
|
||||
registration.update();
|
||||
}
|
||||
};
|
||||
window.addEventListener('focus', handleFocus);
|
||||
|
||||
return () => {
|
||||
clearInterval(checkInterval);
|
||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||
window.removeEventListener('focus', handleFocus);
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Service Worker registration error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
checkForUpdates();
|
||||
|
||||
// Check immediately on load
|
||||
navigator.serviceWorker.getRegistration().then(reg => {
|
||||
if (reg) {
|
||||
reg.update();
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleUpdate = async () => {
|
||||
setIsUpdating(true);
|
||||
|
||||
if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
|
||||
// Send message to service worker to skip waiting
|
||||
navigator.serviceWorker.controller.postMessage({ type: 'SKIP_WAITING' });
|
||||
|
||||
// Reload the page after a short delay
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 500);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDismiss = () => {
|
||||
setShowUpdate(false);
|
||||
};
|
||||
|
||||
if (!showUpdate) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-4 left-4 right-4 md:left-auto md:right-4 md:w-96 z-50 animate-in slide-in-from-bottom-5">
|
||||
<div className="bg-white rounded-xl shadow-lg border-2 border-indigo-500 p-4 flex items-center gap-3">
|
||||
<div className="flex-shrink-0 w-10 h-10 bg-indigo-100 rounded-full flex items-center justify-center">
|
||||
<RefreshCw className="w-5 h-5 text-indigo-600" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<p className="font-semibold text-gray-900 text-sm">
|
||||
نسخه جدید آماده است
|
||||
</p>
|
||||
<p className="text-xs text-gray-600 mt-0.5">
|
||||
برای دریافت آخرین تغییرات، اپ را بهروزرسانی کنید
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
onClick={handleUpdate}
|
||||
disabled={isUpdating}
|
||||
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white text-sm font-medium rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
|
||||
>
|
||||
{isUpdating ? (
|
||||
<>
|
||||
<RefreshCw className="w-4 h-4 animate-spin" />
|
||||
در حال بهروزرسانی...
|
||||
</>
|
||||
) : (
|
||||
'بهروزرسانی'
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleDismiss}
|
||||
className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
aria-label="بستن"
|
||||
>
|
||||
<X className="w-4 h-4 text-gray-500" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export function SummaryCard({ param, currentValue, minValue, maxValue, data }: S
|
||||
case 'soil':
|
||||
return <HumidityGauge value={currentValue} type="soil" />
|
||||
case 'lux':
|
||||
return <LuxGauge value={600} max={2000} />
|
||||
return <LuxGauge value={currentValue} max={2000} />
|
||||
case 'gas':
|
||||
return <GasGauge value={currentValue} max={100} />
|
||||
default:
|
||||
|
||||
@@ -36,8 +36,8 @@ export type GreenhouseAlert = {
|
||||
|
||||
export const TABS: { value: TabType; label: string }[] = [
|
||||
{ value: 'summary', label: 'خلاصه' },
|
||||
{ value: 'charts', label: 'گزارش نموداری' },
|
||||
{ value: 'weather', label: 'وضعیت آب و هوا' },
|
||||
{ value: 'charts', label: 'نمودار' },
|
||||
{ value: 'weather', label: 'آب و هوا' },
|
||||
{ value: 'analysis', label: 'تحلیل' },
|
||||
]
|
||||
|
||||
|
||||
12
src/lib/version.ts
Normal file
12
src/lib/version.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export const APP_VERSION = process.env.NEXT_PUBLIC_APP_VERSION || Date.now().toString();
|
||||
|
||||
export async function getAppVersion(): Promise<string> {
|
||||
try {
|
||||
const response = await fetch('/version.json', { cache: 'no-store' });
|
||||
const data = await response.json();
|
||||
return data.version;
|
||||
} catch {
|
||||
return APP_VERSION;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user