feat: Add shared components and styling for icewatch application
All checks were successful
CI / Validate i18n Files (pull_request) Successful in 19s
Dependency Review / Review Dependencies (pull_request) Successful in 26s
CI / TypeScript Type Check (pull_request) Successful in 1m20s
CI / Lint Code (pull_request) Successful in 1m37s
CI / Build Project (pull_request) Successful in 1m32s
CI / Security Checks (pull_request) Successful in 1m35s
CI / Run Tests (Node 20) (pull_request) Successful in 1m42s
CI / Run Tests (Node 18) (pull_request) Successful in 1m49s
Code Quality / Code Quality Checks (pull_request) Successful in 1m57s
CI / Test Coverage (pull_request) Successful in 1m32s
All checks were successful
CI / Validate i18n Files (pull_request) Successful in 19s
Dependency Review / Review Dependencies (pull_request) Successful in 26s
CI / TypeScript Type Check (pull_request) Successful in 1m20s
CI / Lint Code (pull_request) Successful in 1m37s
CI / Build Project (pull_request) Successful in 1m32s
CI / Security Checks (pull_request) Successful in 1m35s
CI / Run Tests (Node 20) (pull_request) Successful in 1m42s
CI / Run Tests (Node 18) (pull_request) Successful in 1m49s
Code Quality / Code Quality Checks (pull_request) Successful in 1m57s
CI / Test Coverage (pull_request) Successful in 1m32s
- Created example-shared-components.html to demonstrate TypeScript-based shared header and footer components. - Added original-style.css for theming with CSS variables and dark mode support. - Introduced style-backup.css for legacy styles. - Developed test-refactored.html for testing map components with Leaflet integration. - Updated deployment documentation to reflect changes in log file paths and service names. - Renamed project from "great-lakes-ice-report" to "icewatch" in package.json and package-lock.json. - Updated Caddyfile for new log file path. - Added S3 bucket policy for public read access to greatlakes-conditions. - Removed old service file and created new systemd service for icewatch.
This commit is contained in:
parent
d9944a6a4c
commit
96e2619aa2
16 changed files with 19 additions and 30 deletions
221
archive/app-refactored.js
Normal file
221
archive/app-refactored.js
Normal file
|
@ -0,0 +1,221 @@
|
|||
/**
|
||||
* Nominatim-only implementation using MapBase
|
||||
*/
|
||||
class NominatimMapApp extends MapBase {
|
||||
constructor() {
|
||||
super();
|
||||
this.initializeMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the Leaflet map
|
||||
*/
|
||||
initializeMap() {
|
||||
this.map = L.map('map').setView([42.9634, -85.6681], 10);
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
maxZoom: 18,
|
||||
}).addTo(this.map);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create custom marker for location
|
||||
*/
|
||||
createMarker(location) {
|
||||
const isPersistent = !!location.persistent;
|
||||
const timeAgo = window.getTimeAgo ? window.getTimeAgo(location.created_at) : '';
|
||||
const persistentText = isPersistent ? '<br><small><strong>📌 Persistent Report</strong></small>' : '';
|
||||
|
||||
const customIcon = this.createCustomIcon(isPersistent);
|
||||
|
||||
const marker = L.marker([location.latitude, location.longitude], {
|
||||
icon: customIcon
|
||||
})
|
||||
.addTo(this.map)
|
||||
.bindPopup(`<strong>${location.address}</strong><br>${location.description || 'No additional details'}<br><small>Reported ${timeAgo}</small>${persistentText}`);
|
||||
|
||||
return marker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create custom icon for markers
|
||||
*/
|
||||
createCustomIcon(isPersistent = false) {
|
||||
const iconColor = isPersistent ? '#28a745' : '#dc3545'; // Green for persistent, red for temporary
|
||||
const iconSymbol = isPersistent ? '🔒' : '⚠️'; // Lock for persistent, warning for temporary
|
||||
|
||||
return L.divIcon({
|
||||
className: 'custom-marker',
|
||||
html: `
|
||||
<div style="
|
||||
background-color: ${iconColor};
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
|
||||
cursor: pointer;
|
||||
">${iconSymbol}</div>
|
||||
`,
|
||||
iconSize: [30, 30],
|
||||
iconAnchor: [15, 15],
|
||||
popupAnchor: [0, -15]
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for addresses using Nominatim
|
||||
*/
|
||||
async searchAddress(query) {
|
||||
try {
|
||||
// Generate multiple search variations for better results
|
||||
const searches = [
|
||||
query, // Original query
|
||||
query.replace(' & ', ' and '), // Replace & with 'and'
|
||||
query.replace(' and ', ' & '), // Replace 'and' with &
|
||||
query.replace(' at ', ' & '), // Replace 'at' with &
|
||||
`${query}, Michigan`, // Add Michigan if not present
|
||||
`${query}, MI`, // Add MI if not present
|
||||
];
|
||||
|
||||
// Add street type variations
|
||||
const streetVariations = [];
|
||||
searches.forEach(search => {
|
||||
streetVariations.push(search);
|
||||
streetVariations.push(search.replace(' St ', ' Street '));
|
||||
streetVariations.push(search.replace(' Street ', ' St '));
|
||||
streetVariations.push(search.replace(' Ave ', ' Avenue '));
|
||||
streetVariations.push(search.replace(' Avenue ', ' Ave '));
|
||||
streetVariations.push(search.replace(' Rd ', ' Road '));
|
||||
streetVariations.push(search.replace(' Road ', ' Rd '));
|
||||
streetVariations.push(search.replace(' Blvd ', ' Boulevard '));
|
||||
streetVariations.push(search.replace(' Boulevard ', ' Blvd '));
|
||||
});
|
||||
|
||||
// Remove duplicates
|
||||
const uniqueSearches = [...new Set(streetVariations)];
|
||||
|
||||
// Try each search variation until we get results
|
||||
for (const searchQuery of uniqueSearches) {
|
||||
try {
|
||||
const response = await fetch(`https://nominatim.openstreetmap.org/search?format=json&extratags=1&limit=5&q=${encodeURIComponent(searchQuery)}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data && data.length > 0) {
|
||||
// Prefer results that are in Michigan/Grand Rapids
|
||||
const michiganResults = data.filter(item =>
|
||||
item.display_name.toLowerCase().includes('michigan') ||
|
||||
item.display_name.toLowerCase().includes('grand rapids') ||
|
||||
item.display_name.toLowerCase().includes(', mi')
|
||||
);
|
||||
|
||||
const results = michiganResults.length > 0 ? michiganResults : data;
|
||||
this.showAutocomplete(results.slice(0, 5));
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`Geocoding failed for: "${searchQuery}"`, error);
|
||||
}
|
||||
}
|
||||
|
||||
// If no results found, show empty autocomplete
|
||||
this.showAutocomplete([]);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error searching addresses:', error);
|
||||
this.showAutocomplete([]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle form submission with enhanced geocoding
|
||||
*/
|
||||
async handleSubmit() {
|
||||
const address = this.addressInput.value.trim();
|
||||
const description = this.descriptionInput.value.trim();
|
||||
|
||||
if (!address) {
|
||||
this.showMessage('Please enter an address', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// If coordinates are not set, try to geocode the address
|
||||
let lat = parseFloat(this.addressInput.dataset.lat);
|
||||
let lon = parseFloat(this.addressInput.dataset.lon);
|
||||
|
||||
if (!lat || !lon || isNaN(lat) || isNaN(lon)) {
|
||||
// Try to geocode the current address value
|
||||
const geocodeResult = await this.geocodeAddress(address);
|
||||
if (geocodeResult) {
|
||||
lat = parseFloat(geocodeResult.lat);
|
||||
lon = parseFloat(geocodeResult.lon);
|
||||
} else {
|
||||
this.showMessage('Please select a valid address from the suggestions', 'error');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Disable submit button
|
||||
this.submitButton.disabled = true;
|
||||
this.submitButton.innerHTML = '<span class="loading-spinner"></span> Submitting...';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/locations', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
address,
|
||||
latitude: lat,
|
||||
longitude: lon,
|
||||
description
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
this.showMessage('Location reported successfully!', 'success');
|
||||
this.resetForm();
|
||||
this.refreshLocations();
|
||||
} else {
|
||||
this.handleSubmitError(data);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error submitting location:', error);
|
||||
this.showMessage('Error submitting location. Please try again.', 'error');
|
||||
} finally {
|
||||
this.submitButton.disabled = false;
|
||||
this.submitButton.innerHTML = '<i class="fas fa-flag"></i> Report Ice';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Geocode an address using Nominatim
|
||||
*/
|
||||
async geocodeAddress(address) {
|
||||
try {
|
||||
const response = await fetch(`https://nominatim.openstreetmap.org/search?format=json&limit=1&q=${encodeURIComponent(address + ', Michigan')}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data && data.length > 0) {
|
||||
return data[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Error geocoding address:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the app when DOM is loaded
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new NominatimMapApp();
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue