Compare commits

8 Commits

Author SHA1 Message Date
Jake Kasper
4a6728ee4e fix button 2025-09-08 13:43:04 -04:00
Jake Kasper
b52e21838c no background logo 2025-09-05 15:43:09 -04:00
Jake Kasper
dc7b1b8c84 j,hk 2025-09-05 15:41:23 -04:00
Jake Kasper
6fdaad5b0c asdfasdf 2025-09-05 15:33:49 -04:00
Jake Kasper
8cf3e379dc asdfasfd 2025-09-05 15:31:38 -04:00
Jake Kasper
0be71523a4 logo 2025-09-05 15:28:44 -04:00
Jake Kasper
8416a9daf7 update to turftracking 2025-09-05 15:05:32 -04:00
Jake Kasper
a3112efee0 asdfasdf 2025-09-05 14:55:10 -04:00
13 changed files with 69 additions and 55 deletions

View File

@@ -1,6 +1,6 @@
# TurfTracker - Professional Lawn Care Management # TurfTracking - Professional Lawn Care Management
TurfTracker is a comprehensive web application designed for homeowners to track and manage their lawn care activities including fertilizer applications, weed control, mowing schedules, and equipment management. TurfTracking is a comprehensive web application designed for homeowners to track and manage their lawn care activities including fertilizer applications, weed control, mowing schedules, and equipment management.
## Features ## Features
@@ -76,7 +76,7 @@ TurfTracker is a comprehensive web application designed for homeowners to track
- Docker and Docker Compose - Docker and Docker Compose
- Traefik reverse proxy running with `proxy` network - Traefik reverse proxy running with `proxy` network
- DNS pointing `turftracker.kaspers.us` to your server - DNS pointing `turftracker.kaspers.us` to your server (or your chosen domain)
- Git - Git
### Traefik Configuration Notes ### Traefik Configuration Notes
@@ -92,7 +92,7 @@ This application is pre-configured for deployment behind Traefik with:
1. **Clone the repository** 1. **Clone the repository**
```bash ```bash
git clone <repository-url> git clone <repository-url>
cd turftracker cd turftracking
``` ```
2. **Environment Configuration** 2. **Environment Configuration**
@@ -235,8 +235,8 @@ If you have an Authentik instance for SSO:
3. **In Authentik Applications:** 3. **In Authentik Applications:**
- Create new Application - Create new Application
- Set Name: `TurfTracker` - Set Name: `TurfTracking`
- Set Slug: `turftracker` - Set Slug: `turftracking`
- Set Provider: (select the provider created above) - Set Provider: (select the provider created above)
**Scopes Required:** `openid profile email` **Scopes Required:** `openid profile email`
@@ -244,7 +244,7 @@ If you have an Authentik instance for SSO:
## Application Structure ## Application Structure
``` ```
turftracker/ turftracking/
├── backend/ # Node.js API server ├── backend/ # Node.js API server
│ ├── src/ │ ├── src/
│ │ ├── routes/ # API endpoints │ │ ├── routes/ # API endpoints
@@ -373,4 +373,4 @@ For questions or issues:
- Use strong JWT secrets - Use strong JWT secrets
- Enable HTTPS in production - Enable HTTPS in production
- Regularly update dependencies - Regularly update dependencies
- Follow security best practices for API key management - Follow security best practices for API key management

View File

@@ -1,7 +1,7 @@
{ {
"name": "turftracker-backend", "name": "turftracking-backend",
"version": "1.0.0", "version": "1.0.0",
"description": "Backend API for TurfTracker lawn care management application", "description": "Backend API for TurfTracking lawn care management application",
"main": "src/app.js", "main": "src/app.js",
"scripts": { "scripts": {
"start": "node src/app.js", "start": "node src/app.js",
@@ -38,6 +38,6 @@
"fertilizer", "fertilizer",
"turf management" "turf management"
], ],
"author": "TurfTracker Team", "author": "TurfTracking Team",
"license": "MIT" "license": "MIT"
} }

View File

@@ -136,7 +136,7 @@ process.on('SIGINT', () => {
}); });
app.listen(PORT, '0.0.0.0', () => { app.listen(PORT, '0.0.0.0', () => {
console.log(`TurfTracker API server running on port ${PORT}`); console.log(`TurfTracking API server running on port ${PORT}`);
console.log(`Environment: ${process.env.NODE_ENV || 'development'}`); console.log(`Environment: ${process.env.NODE_ENV || 'development'}`);
}); });
// Disable etags and set no-store by default to avoid stale cached API responses // Disable etags and set no-store by default to avoid stale cached API responses

View File

@@ -13,41 +13,24 @@ const calculatePolygonArea = (coordinates) => {
console.log('Invalid coordinates for area calculation:', coordinates); console.log('Invalid coordinates for area calculation:', coordinates);
return 0; return 0;
} }
console.log('Calculating area for coordinates:', coordinates); console.log('Calculating area for coordinates:', coordinates);
// Shoelace formula for polygon area
// Coordinates are in [latitude, longitude] format (not GeoJSON standard)
let area = 0; let area = 0;
const n = coordinates.length; const n = coordinates.length;
for (let i = 0; i < n; i++) { for (let i = 0; i < n; i++) {
const j = (i + 1) % n; const j = (i + 1) % n;
// coordinates[i][0] = latitude, coordinates[i][1] = longitude area += coordinates[i][1] * coordinates[j][0];
area += coordinates[i][1] * coordinates[j][0]; // lng * lat area -= coordinates[j][1] * coordinates[i][0];
area -= coordinates[j][1] * coordinates[i][0]; // lng * lat
} }
area = Math.abs(area) / 2; area = Math.abs(area) / 2;
console.log('Raw shoelace area (in deg²):', area); console.log('Raw shoelace area (in deg²):', area);
const avgLat = coordinates.reduce((sum, coord) => sum + coord[0], 0) / n;
// Convert from decimal degrees to square feet
// coordinates are [latitude, longitude] format
const avgLat = coordinates.reduce((sum, coord) => sum + coord[0], 0) / n; // coord[0] is latitude
console.log('Average latitude:', avgLat); console.log('Average latitude:', avgLat);
const metersPerDegreeLat = 111320;
// More accurate conversion using Haversine-based approach const metersPerDegreeLng = 111320 * Math.cos(avgLat * Math.PI / 180);
const latInRadians = avgLat * Math.PI / 180;
const metersPerDegreeLat = 111320; // meters per degree latitude (constant)
const metersPerDegreeLng = 111320 * Math.cos(latInRadians); // meters per degree longitude (varies with latitude)
// Convert to square meters, then to square feet
const areaInSquareMeters = area * metersPerDegreeLat * metersPerDegreeLng; const areaInSquareMeters = area * metersPerDegreeLat * metersPerDegreeLng;
const areaInSquareFeet = areaInSquareMeters * 10.7639; // 1 m² = 10.7639 ft² const areaInSquareFeet = areaInSquareMeters * 10.7639;
console.log('Area in square meters:', areaInSquareMeters); console.log('Area in square meters:', areaInSquareMeters);
console.log('Area in square feet:', areaInSquareFeet); console.log('Area in square feet:', areaInSquareFeet);
return areaInSquareFeet; return areaInSquareFeet;
}; };

View File

@@ -1,7 +1,7 @@
{ {
"name": "turftracker-frontend", "name": "turftracking-frontend",
"version": "1.0.0", "version": "1.0.0",
"description": "Frontend React application for TurfTracker lawn care management", "description": "Frontend React application for TurfTracking lawn care management",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@testing-library/jest-dom": "^5.17.0", "@testing-library/jest-dom": "^5.17.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 670 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

View File

@@ -2,11 +2,16 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <!-- Favicon & brand icons -->
<!-- Use PNG favicon for broad compatibility -->
<link rel="icon" type="image/png" href="%PUBLIC_URL%/brand/logo.png" />
<!-- Precise favicon sizes -->
<link rel="icon" type="image/png" sizes="32x32" href="%PUBLIC_URL%/brand/favicon-32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="%PUBLIC_URL%/brand/favicon-16.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#059669" /> <meta name="theme-color" content="#059669" />
<meta name="description" content="TurfTracker - Professional lawn care management and tracking application" /> <meta name="description" content="TurfTracking - Professional lawn care management and tracking application" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/brand/apple-touch-icon.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!-- Google Fonts --> <!-- Google Fonts -->
@@ -17,9 +22,9 @@
<!-- Progressive Web App meta tags --> <!-- Progressive Web App meta tags -->
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default"> <meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="TurfTracker"> <meta name="apple-mobile-web-app-title" content="TurfTracking">
<title>TurfTracker - Lawn Care Management</title> <title>TurfTracking - Lawn Care Management</title>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
@@ -29,7 +34,7 @@
<div id="loading-indicator" class="fixed inset-0 flex items-center justify-center bg-green-50 z-50"> <div id="loading-indicator" class="fixed inset-0 flex items-center justify-center bg-green-50 z-50">
<div class="text-center"> <div class="text-center">
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-green-600 mx-auto mb-4"></div> <div class="animate-spin rounded-full h-12 w-12 border-b-2 border-green-600 mx-auto mb-4"></div>
<h2 class="text-xl font-semibold text-green-800">TurfTracker</h2> <h2 class="text-xl font-semibold text-green-800">TurfTracking</h2>
<p class="text-green-600">Loading your lawn care dashboard...</p> <p class="text-green-600">Loading your lawn care dashboard...</p>
</div> </div>
</div> </div>
@@ -46,4 +51,4 @@
}); });
</script> </script>
</body> </body>
</html> </html>

View File

@@ -0,0 +1,20 @@
{
"short_name": "TurfTracking",
"name": "TurfTracking",
"icons": [
{
"src": "/brand/logo.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "/brand/logo.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": "/",
"display": "standalone",
"theme_color": "#059669",
"background_color": "#ffffff"
}

View File

@@ -20,7 +20,10 @@ const AuthLayout = ({ children }) => {
/> />
</svg> </svg>
</div> </div>
<h1 className="text-3xl font-bold text-gray-900 mb-2">TurfTracker</h1> <div className="flex items-center gap-2 mb-2">
<img src="/brand/logo.png" alt="TurfTracking" className="h-8 w-8" />
<h1 className="text-3xl font-bold text-gray-900">TurfTracking</h1>
</div>
<p className="text-lg text-gray-600">Professional Lawn Care Management</p> <p className="text-lg text-gray-600">Professional Lawn Care Management</p>
</div> </div>
</div> </div>
@@ -40,4 +43,4 @@ const AuthLayout = ({ children }) => {
); );
}; };
export default AuthLayout; export default AuthLayout;

View File

@@ -144,8 +144,10 @@ const Layout = ({ children }) => {
onClick={() => setSidebarOpen(false)} onClick={() => setSidebarOpen(false)}
/> />
<div className="fixed inset-y-0 left-0 flex w-64 flex-col bg-white shadow-xl"> <div className="fixed inset-y-0 left-0 flex w-64 flex-col bg-white shadow-xl">
<div className="flex h-16 items-center justify-between px-6 border-b border-gray-200"> <div className="relative flex h-20 items-center px-6 border-b border-gray-200">
<h1 className="text-xl font-bold text-primary-600">TurfTracker</h1> <div className="absolute inset-0 flex items-center justify-center pointer-events-none">
<img src="/brand/logo.png" alt="TurfTracking" className="h-16 w-16 object-contain" />
</div>
<button <button
type="button" type="button"
className="text-gray-400 hover:text-gray-600" className="text-gray-400 hover:text-gray-600"
@@ -243,8 +245,8 @@ const Layout = ({ children }) => {
{/* Desktop sidebar */} {/* Desktop sidebar */}
<div className="hidden lg:fixed lg:inset-y-0 lg:flex lg:w-64 lg:flex-col"> <div className="hidden lg:fixed lg:inset-y-0 lg:flex lg:w-64 lg:flex-col">
<div className="flex min-h-0 flex-1 flex-col bg-white border-r border-gray-200"> <div className="flex min-h-0 flex-1 flex-col bg-white border-r border-gray-200">
<div className="flex h-16 items-center px-6 border-b border-gray-200"> <div className="flex h-24 items-center justify-center px-6 border-b border-gray-200">
<h1 className="text-xl font-bold text-primary-600">TurfTracker</h1> <img src="/brand/logo.png" alt="TurfTracking" className="h-20 w-20 object-contain" />
</div> </div>
<nav className="flex-1 space-y-1 px-4 py-6"> <nav className="flex-1 space-y-1 px-4 py-6">
@@ -328,7 +330,7 @@ const Layout = ({ children }) => {
<div className="lg:pl-64"> <div className="lg:pl-64">
{/* Top bar */} {/* Top bar */}
<div className="sticky top-0 z-10 bg-white border-b border-gray-200 lg:hidden"> <div className="sticky top-0 z-10 bg-white border-b border-gray-200 lg:hidden">
<div className="flex h-16 items-center justify-between px-4"> <div className="relative flex h-16 items-center justify-between px-4">
<button <button
type="button" type="button"
className="text-gray-500 hover:text-gray-600" className="text-gray-500 hover:text-gray-600"
@@ -336,9 +338,10 @@ const Layout = ({ children }) => {
> >
<Bars3Icon className="h-6 w-6" /> <Bars3Icon className="h-6 w-6" />
</button> </button>
{/* Centered logo (click-through so it won't block buttons) */}
<h1 className="text-lg font-semibold text-gray-900">TurfTracker</h1> <div className="absolute inset-0 flex items-center justify-center pointer-events-none">
<img src="/brand/logo.png" alt="TurfTracking" className="h-12 w-12 object-contain" />
</div>
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
<button className="text-gray-400 hover:text-gray-500"> <button className="text-gray-400 hover:text-gray-500">
<BellIcon className="h-6 w-6" /> <BellIcon className="h-6 w-6" />