//src/utils/Connectivity.js

let isOnline = navigator.onLine;

const updateOnlineStatus = () => {
	
	isOnline = navigator.onLine;
};

window.addEventListener('load', updateOnlineStatus);
window.addEventListener('online', updateOnlineStatus);
window.addEventListener('offline', updateOnlineStatus);

export const checkOnlineStatus = () => isOnline;

import IndexedDB from '../utils/IndexedDB';
import { checkOnlineStatus } from '../utils/Connectivity';
import logger from '../utils/logger';
import { showAlert } from '../widgets/Alert';

/**
* Data Structure
*
[
    {
        "id": "layout-1",
        "blocks": [
            {
                "i": "block-text-0",
                "x": 0,
                "y": 0,
                "w": 1,
                "h": 2,
                "content": "Text Block 0",
                "type": "text"
            }
        ]
    },
    {
        "id": "layout-2",
        "blocks": [
            {
                "i": "block-text-1",
                "x": 1,
                "y": 0,
                "w": 2,
                "h": 3,
                "content": "Text Block 1",
                "type": "text"
            }
        ]
    }
]
*/
const layoutDB = new IndexedDB('ReactCard', 1);

// .ENV API URL
const API_URL = process.env.REACT_APP_API_URL;
const LOG_LEVEL = process.env.REACT_APP_LOG_LEVEL;

/**
* IndexedDB Broswer Database
* Ensure the database is opened and the store is created
*/
const openDatabase = async () => {
	
	try {
		await layoutDB.initialize();
	}
	catch (error) {
		
		logger.error("Failed to open IndexedDB:", error);
		
		throw error;
	}
};

/**
* Fetch Data
* Fetch layout data from the API or use dummy data if offline
*/
export const fetchLayoutData = async (layoutId) => {
	
	await openDatabase();// Ensure the IndexedDB is opened
	let response;// Declare 'response' var for scope access
	if (LOG_LEVEL === 'deve') {
		
		const response = await fetch('../../../dummyData.json');
		if (!response.ok) {
			
			throw new Error("Failed to fetch dummy data");
		}
		
		const dummyData = await response.json();
		await layoutDB.clearStore('LayoutData');
		
		// Add timestamps to dummy data
		const layoutTimestamp = dummyData.map(layout => ({
			...layout,
			created_at: Date.now(),
			updated_at: Date.now()
		}));
		await Promise.all(layoutTimestamp.map(layout => layoutDB.putData('LayoutData', layout)));
		
		return layoutTimestamp.find(layout => layout.id === layoutId)?.blocks ?? [];
	}
	
	try {
		if (checkOnlineStatus()) {
			
			// Online fetch from the API
			const response = await fetch(`${API_URL}/api/fetch/${layoutId}`);
			if (!response.ok) {
				
				throw new Error("Failed to fetch layout data from API");
			}
			
			const apiLayoutData = await response.json();
			
			// Include timestamps when saving to IndexedDB
			const layoutTimestamp = {
				id: layoutId,
				blocks: apiLayoutData.blocks,
				created_at: apiLayoutData.created_at || Date.now(),// Use API created_at if available
				updated_at: Date.now()// Set this to now
			};
			await layoutDB.putData('LayoutData', layoutTimestamp);
			
			return apiLayoutData.blocks;
		}
		else {
			// Offline fetch from IndexedDB
			logger.log("Fetching layout data from IndexedDB for offline use.");
		
			const storedLayoutData = await layoutDB.getData('LayoutData', layoutId);
			return storedLayoutData?.blocks ?? [];// Return blocks or empty array
		}
	}
	catch (error) {
		
		logger.error("Error fetching layout data:", error);
		
		throw error;
	}
};
	
/**
* Save Data
* Save layout data
*/
export const saveLayoutData = async (layoutId, layoutData) => {
	
	await openDatabase();
	const currentTimestamp = Date.now();
	let response;
	try {
		if (checkOnlineStatus()) {
		
			const response = await fetch(`${API_URL}/api/save/${layoutId}`, {
				method: 'POST',
				headers: {
					'Content-Type': 'application/json',
				},
				body: JSON.stringify({
					...layoutData,
					created_at: layoutData.created_at || currentTimestamp,// Keep original created_at if exists
					updated_at: currentTimestamp,// Always update this
				});
			});
			if (!response.ok) {
				
				throw new Error("Failed to save layout data to API");
			}
			
			// If saving to the server succeeds, save the data to IndexedDB
			await layoutDB.putData('LayoutData', {
				id: layoutId,
				blocks: layoutData,
				created_at: layoutData.created_at || currentTimestamp,
				updated_at: currentTimestamp,
			});
			
			logger.log(`Layout data for ${layoutId} saved successfully to API and IndexedDB.`);
		}
		else {
			// Offline case
			await layoutDB.putData('LayoutData', {
				id: layoutId,
				blocks: layoutData,
				created_at: layoutData.created_at || currentTimestamp,
				updated_at: currentTimestamp,
			});
			
			showAlert('warning', 'IndexedDB (offline)');
			logger.log(`Layout data for ${layoutId} saved successfully in IndexedDB (offline).`);
			
			// TODO TODO TODO If offline, need a way to check the status and update the database once back online.
			// connectivity.js needs updated.
		}
	}
	catch (error) {
		
		logger.error("Error saving layout data:", error);
		
		throw error;
	}
};

/**
* Delete Data
*/
export const deleteLayoutData = async (layoutId) => {
	
	await openDatabase();
	let response;
	try {
		if (checkOnlineStatus()) {
			
			const response = await fetch(`${API_URL}/api/delete/${layoutId}`, {
				method: 'DELETE',// Use DELETE to remove the layout
				headers: {
					'Content-Type': 'application/json',
				},
			});
			
			if (!response.ok) {
			
				throw new Error(`Failed to delete layout from API: ${response.statusText}`);
			}
			
			// Delete from IndexedDB
			await layoutDB.deleteData('LayoutData', layoutId);
			
			logger.log(`Layout data for ${layoutId} deleted successfully from API and IndexedDB.`);
		}
		else {
			// Offline case, delete only from IndexedDB
			await layoutDB.deleteData('LayoutData', layoutId);
			
			showAlert('warning', 'IndexedDB (offline)');
			logger.log(`Layout data for ${layoutId} deleted successfully from IndexedDB (offline).`);
		}
	}
	catch (error) {
		
		logger.error("Error deleting layout data:", error);
		
		throw error;
	}
};

//src/utils/IndexedDB.js
import logger from './logger';

class IndexedDB {
	
	constructor(dbName, version) {
		
		this.dbName = dbName;
		this.version = version;
		this.db = null;
	}
	
	async initialize() {
	
		try {
			await this.open();
			
			logger.log('Database opened successfully.');
		}
		catch (error) {
			
			logger.error('Database failed to open:', error);
			
			throw error;
		}
	}
	
	/**
	* Open
	* Open the database
	*/
	async open() {
		
		if (this.db) {
			
			logger.log('Database is already initialized.');
			
			return this.db;
		}
		
		return new Promise((resolve, reject) => {
			
			const request = indexedDB.open(this.dbName, this.version);
			request.onsuccess = (event) => {
				
				this.db = event.target.result;
				resolve(this.db);
			};
			
			request.onerror = (event) => {
				
				reject(`Error opening DB: ${event.target.errorCode}`);
			};
			
			request.onupgradeneeded = (event) => {
				
				const db = event.target.result;
				if (!db.objectStoreNames.contains('LayoutData')) {
					
					db.createObjectStore('LayoutData', { keyPath: 'id' });
				}
			};
		});
	}
	
	/**
	* Put
	* Put data into a specified object store
	*/
	async putData(storeName, data) {
		
		this.checkDB();// Ensure the database is initialized
		const transaction = this.db.transaction(storeName, 'readwrite');
		const store = transaction.objectStore(storeName);
		
		return new Promise((resolve, reject) => {
			
			const request = store.put(data);
			request.onsuccess = () => resolve();
			request.onerror = () => reject('Error putting data');
		});
	}
	
	/**
	* Get Data
	* Get data from a specified object store
	*/
	async getData(storeName, id) {
		
		this.checkDB();
		const transaction = this.db.transaction(storeName, 'readonly');
		const store = transaction.objectStore(storeName);
		
		return new Promise((resolve, reject) => {
		
			const request = store.get(id);// Ensure to create the request first
			request.onsuccess = (event) => resolve(event.target.result);
			request.onerror = () => reject('Error getting data');
		});
	}
	
	/**
	* Delete Data
	* Delete data from a specified object store
	*/
	async deleteData(storeName, id) {
		
		this.checkDB();
		const transaction = this.db.transaction(storeName, 'readwrite');
		const store = transaction.objectStore(storeName);
		
		return new Promise((resolve, reject) => {
			
			const request = store.delete(id);// Ensure to create the request first
			request.onsuccess = () => resolve();
			request.onerror = () => reject('Error deleting data');
		});
	}
	
	/**
	* Clear Data
	* Clear data from a specified object store
	*/
	async clearStore(storeName) {
		
		this.checkDB();
		const transaction = this.db.transaction(storeName, 'readwrite');
		const store = transaction.objectStore(storeName);
		
		return new Promise((resolve, reject) => {
			
			const request = store.clear();
			request.onsuccess = () => resolve();
			request.onerror = () => reject('Error clearing the store');
		});
	}
	
	/**
	* Check if Database is open
	*/
	checkDB() {
		
		if (!this.db) {
			
			throw new Error('Database not initialized. Please open it first.');
		}
	}
	
	/**
	* Close the database
	*/
	close() {
		
		if (this.db) {
			
			this.db.close();
			this.db = null;// Clear the db reference
		}
	}
}

export default IndexedDB;


This is my setup for handling data from LayoutSerivce to IndexedDB to Connectivity. My question is this: Right now I was considering adding a 
alart that shows when the user is offline, warning them that all data is being saved in the broswer and if they close it before going back
online, it proptentionally could be lost. Now at first i was thinking of adding this message in the fetch and save of the LayoutService
But then I wonder since Connectivity is where the check is, if it should happen there.. But not only a check, but if the user is offline
I should have some sort of code to check every so often for a connect to update the database.