fix bug and version check
Some checks failed
Deploy MyApp on Same Server / build-and-deploy (push) Failing after 1s

This commit is contained in:
2025-12-18 19:39:05 +03:30
parent 2481381798
commit 31b58b7151
12 changed files with 300 additions and 35 deletions

View File

@@ -3,7 +3,7 @@ import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
reactStrictMode: true,
experimental: {},
turbopack: { root: __dirname }
turbopack: { root: __dirname },
}
export default nextConfig

View File

@@ -4,7 +4,7 @@
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"build": "node scripts/generate-version.js && next build",
"start": "next start",
"lint": "eslint"
},

View File

@@ -1,5 +1,5 @@
const CACHE_NAME = 'greenhome-v2';
const STATIC_CACHE_NAME = 'greenhome-static-v2';
const CACHE_NAME = 'greenhome-1766074058129';
const STATIC_CACHE_NAME = 'greenhome-static-1766074058129';
// Static assets to cache on install
const STATIC_FILES_TO_CACHE = [
@@ -32,7 +32,8 @@ self.addEventListener('install', (event) => {
} catch (error) {
console.log('Some static files failed to cache:', error);
}
await self.skipWaiting();
// Don't skip waiting automatically - let user decide when to update
// await self.skipWaiting();
})()
);
});
@@ -53,6 +54,20 @@ self.addEventListener('activate', (event) => {
);
});
// Listen for messages from clients (e.g., when user clicks update button)
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting().then(() => {
// Notify all clients about update
return self.clients.matchAll();
}).then((clients) => {
clients.forEach((client) => {
client.postMessage({ type: 'SW_UPDATED' });
});
});
}
});
self.addEventListener('fetch', (event) => {
const { request } = event;
const url = new URL(request.url);

3
public/version.json Normal file
View File

@@ -0,0 +1,3 @@
{
"version": "1766074058129"
}

View File

@@ -0,0 +1,38 @@
const fs = require('fs');
const path = require('path');
const version = Date.now().toString();
const versionFile = path.join(__dirname, '../public/version.json');
// Ensure public directory exists
const publicDir = path.dirname(versionFile);
if (!fs.existsSync(publicDir)) {
fs.mkdirSync(publicDir, { recursive: true });
}
fs.writeFileSync(versionFile, JSON.stringify({ version }, null, 2));
// Also write to .env.local for Next.js to use
const envFile = path.join(__dirname, '../.env.local');
const envContent = `NEXT_PUBLIC_APP_VERSION=${version}\n`;
fs.writeFileSync(envFile, envContent);
// Update cache name in sw.js based on version
const swPath = path.join(__dirname, '../public/sw.js');
if (fs.existsSync(swPath)) {
let swContent = fs.readFileSync(swPath, 'utf8');
// Update cache names with version - using more specific regex
const cacheNameRegex = /const CACHE_NAME\s*=\s*['"][^'"]*['"]/;
const staticCacheNameRegex = /const STATIC_CACHE_NAME\s*=\s*['"][^'"]*['"]/;
if (cacheNameRegex.test(swContent)) {
swContent = swContent.replace(cacheNameRegex, `const CACHE_NAME = 'greenhome-${version}'`);
}
if (staticCacheNameRegex.test(swContent)) {
swContent = swContent.replace(staticCacheNameRegex, `const STATIC_CACHE_NAME = 'greenhome-static-${version}'`);
}
fs.writeFileSync(swPath, swContent);
}
console.log(`Version generated: ${version}`);

View File

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

View File

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

View File

@@ -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);
};
}
}, []);

View 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>
);
}

View File

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

View File

@@ -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
View 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;
}
}