Initial Commit

This commit is contained in:
2026-04-25 16:08:12 +00:00
commit 66eca93f01
13 changed files with 854 additions and 0 deletions

42
.gitignore vendored Normal file
View File

@@ -0,0 +1,42 @@
# Virtual environment
.venv/
env/
venv/
# Python bytecode and cache
__pycache__/
*.pyc
*.pyo
*.pyd
# Flask specific
instance/
.webassets-cache/
# Testing
.pytest_cache/
.coverage
htmlcov/
# Build artifacts
dist/
build/
*.egg-info/
# Editor specific files (optional, uncomment if applicable)
# .vscode/
# .idea/
# *.sublime-project
# *.sublime-workspace
# Database files (if using a local SQLite database)
*.db
*.sqlite
*.sqlite3
# Log files
*.log
# Sensitive files (e.g., environment variables)
.env

265
app.py Normal file
View File

@@ -0,0 +1,265 @@
import os
from flask import Flask, render_template, request, redirect, url_for, session, flash, g, jsonify
from dotenv import load_dotenv
import sqlite3
from datetime import timedelta
import requests
from google.oauth2 import service_account
from googleapiclient.discovery import build
import json
import logging
load_dotenv()
app = Flask(__name__)
app.secret_key = os.getenv('FLASK_SECRET_KEY', os.urandom(24))
app.permanent_session_lifetime = timedelta(minutes=30)
app.config['SESSION_COOKIE_PATH'] = '/'
USERNAME = os.getenv('FLASK_LOGIN_USER')
PASSWORD = os.getenv('FLASK_LOGIN_PASSWORD')
DATABASE = 'work_orders.db'
PUSHOVER_API_TOKEN = os.getenv('PUSHOVER_API_TOKEN')
PUSHOVER_USER_KEY = os.getenv('PUSHOVER_USER_KEY')
GA_PROPERTY_ID = os.getenv('GA_PROPERTY_ID')
GA_CREDENTIALS = 'ga_key.json'
logging.basicConfig(
filename='/var/log/ga4_debug.log', # Use an absolute path in production
level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(message)s'
)
def get_db():
db = getattr(g, '_database', None)
if db is None:
db = g._database = sqlite3.connect(DATABASE)
return db
@app.teardown_appcontext
def close_connection(exception):
db = getattr(g, '_database', None)
if db is not None:
db.close()
def init_db():
with app.app_context():
db = get_db()
# Orders table
db.execute('''CREATE TABLE IF NOT EXISTS orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
job TEXT NOT NULL,
address TEXT NOT NULL,
city TEXT NOT NULL,
state TEXT NOT NULL,
zipcode INTEGER NOT NULL,
phone TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);''')
# Completed Orders table
db.execute('''CREATE TABLE IF NOT EXISTS completed_orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
job TEXT NOT NULL,
address TEXT NOT NULL,
city TEXT NOT NULL,
state TEXT NOT NULL,
zipcode INTEGER NOT NULL,
phone TEXT NOT NULL,
completed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);''')
db.commit()
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
if username == USERNAME and password == PASSWORD:
session.permanent = True
session['logged_in'] = True
return redirect(url_for('admin'))
else:
flash('Invalid username or password.')
return render_template('login.html')
@app.route('/logout', methods=['GET', 'POST'])
def logout():
session.pop('logged_in', None)
flash('You have been logged out.')
return redirect(url_for('index'))
@app.route('/')
def index():
return render_template('form.html')
@app.route('/submit', methods=['POST'])
def submit():
try:
name = request.form.get('name')
job = request.form.get('job')
address = request.form.get('address')
city = request.form.get('city')
state = request.form.get('state')
zipcode = request.form.get('zipcode')
phone = request.form.get('phone')
conn = get_db()
conn.execute('''INSERT INTO orders (name, job, address, city, state, zipcode, phone)
VALUES (?, ?, ?, ?, ?, ?, ?)''', (name, job, address, city, state, zipcode, phone))
conn.commit()
conn.close()
send_push_notification(name, job)
session['form_submitted'] = True
return redirect(url_for('success'))
except Exception as e:
session['form_submitted'] = True
return redirect(url_for('failure'))
@app.route('/success')
def success():
if not session.pop('form_submitted', None):
return redirect(url_for('index'))
return render_template('success.html')
@app.route('/failure')
def failure():
if not session.pop('form_submitted', None):
return redirect(url_for('index'))
return render_template('failure.html')
@app.route('/admin')
def admin():
if not session.get('logged_in'):
return redirect(url_for('login'))
conn = get_db()
orders = conn.execute('SELECT * FROM orders').fetchall()
conn.close()
return render_template('admin.html', work_orders=orders)
@app.route('/delete_order/<int:order_id>', methods=['POST'])
def delete_order(order_id):
if not session.get('logged_in'):
return redirect(url_for('login'))
try:
db = get_db()
db.execute('DELETE FROM orders WHERE id = ?', (order_id,))
db.commit()
flash('Work order deleted successfully.', 'success')
except Exception as e:
flash(f'Error deleting work order: {str(e)}', 'danger')
return redirect(url_for('admin'))
@app.route('/mark_complete/<int:order_id>', methods=['POST'])
def mark_complete(order_id):
if not session.get('logged_in'):
return redirect(url_for('login'))
try:
db = get_db()
# Fetch the order to mark as complete
order = db.execute('SELECT * FROM orders WHERE id = ?', (order_id,)).fetchone()
if order:
db.execute('INSERT INTO completed_orders (name, job, address, city, state, zipcode, phone) VALUES (?, ?, ?, ?, ?, ?, ?)',
(order[1], order[2], order[3], order[4], order[5], order[6], order[7]))
db.execute('DELETE FROM orders WHERE id = ?', (order_id,))
db.commit()
flash('Work order marked as complete.', 'success')
else:
flash('Work order not found.', 'danger')
except Exception as e:
flash(f'Error marking work order as complete: {str(e)}', 'danger')
return redirect(url_for('admin'))
@app.route('/completed_jobs')
def completed_jobs():
if not session.get('logged_in'):
return redirect(url_for('login'))
db = get_db()
jobs = db.execute('SELECT * FROM completed_orders').fetchall()
return render_template('completed_jobs.html', completed_jobs=jobs)
def send_push_notification(name, job):
if not PUSHOVER_API_TOKEN or not PUSHOVER_USER_KEY:
print("Pushover API token or user key is missing.")
return
message = f"New Work Order:\nName: {name}\nJob: {job}"
data = {
"token": PUSHOVER_API_TOKEN,
"user": PUSHOVER_USER_KEY,
"message": message
}
response = requests.post("https://api.pushover.net/1/messages.json", data=data)
if response.status_code == 200:
print("Push notification sent!")
else:
print(f"Failed to send notification: {response.status_code} - {response.text}")
def get_ga4_data():
"""Fetch website analytics data from Google Analytics 4 (GA4) and handle missing data."""
try:
credentials = service_account.Credentials.from_service_account_file(
GA_CREDENTIALS, scopes=["https://www.googleapis.com/auth/analytics.readonly"]
)
analytics = build("analyticsdata", "v1beta", credentials=credentials)
response = analytics.properties().runReport(
property=f"properties/{int(GA_PROPERTY_ID)}",
body={
"dateRanges": [{"startDate": "7daysAgo", "endDate": "yesterday"}],
"metrics": [{"name": "totalUsers"}, {"name": "screenPageViews"}],
"dimensions": [{"name": "pagePath"}],
"limit": 1,
},
).execute()
logging.debug("Full GA4 API Response: %s", json.dumps(response, indent=2))
# Extract top page from rows
rows = response.get("rows", [])
if not rows:
logging.error("No rows found in GA4 response!")
return {"total_users": "0", "total_pageviews": "0", "top_page": "N/A"}
top_page = rows[0]["dimensionValues"][0]["value"] if rows else "N/A"
# Extract users and pageviews from the first row
metric_values = rows[0]["metricValues"]
total_users = metric_values[0]["value"] if len(metric_values) > 0 else "0"
total_pageviews = metric_values[1]["value"] if len(metric_values) > 1 else "0"
logging.debug(f"Extracted Users: {total_users}, Pageviews: {total_pageviews}")
return {"total_users": total_users, "total_pageviews": total_pageviews, "top_page": top_page}
except Exception as e:
logging.error("Google Analytics API Error: %s", str(e))
return {"total_users": "0", "total_pageviews": "0", "top_page": "N/A"}
@app.route('/analytics')
def analytics():
if not session.get('logged_in'):
return redirect(url_for('login'))
return jsonify(get_ga4_data())
if __name__ == '__main__':
init_db()
app.run(debug=True)

13
ga_key.json Normal file
View File

@@ -0,0 +1,13 @@
{
"type": "service_account",
"project_id": "thors-hammer-electrical",
"private_key_id": "79f95f7175cb393373b06fb18140b11cb63f7fe5",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC5jfg3AOV4mloj\ntOF8h6UbZWky3VgVIu5AMEeX7gMzQpxNX6cGorPHHyvXxePfBAq/n6Njb3iZ+Nc3\nb3IEtkhhObA9M8i9e6NLPbh5ODKJdlqGwwJ3Cd1hKEKdS4ssnQ9bbwyHI1uikl+c\ndAsW2ZNUtRlSSLt5h/t8oO+Fwr4p4PdMHcBtk8jA9aIrs5IRs7aDat6Kp+/WSwqv\nMdZ3Mj5pwZGIgzKsByXFDX7gbtZRHdJE467pJrmbgRtutOJQijHp0NZxosij5gYU\nyiZTewMdwCWUHhBlnBsn980szCO8IufurTSlEdL4priCz6MdzziMigYomsa1LFYC\nCY/LGegzAgMBAAECggEAHZTUjXBHJQL2e9rKV+AIImX4b3N6J2R6NyF7SG1ZdUKZ\nSHyHVDd8EbKWer/BpDwwunvowGF1CJbzOJM7yiSFRXq87gTja9HaJpSgZDLhW6jS\npclRC0k3UdXpSMpSVbp0SE9b3+9zHGfZdkfJvMrTAh4c+1E9EhLrtOKzTM/PIT3G\nda++giIYESFcx7ls2cqFED4U33pL9/EpRbE+6rZ1CwzJqWYbnN8joD1GeFMAu1lP\nQV1Yjzge+53mF2Tqr05SuYQNXExxW6BegUNQ3E/D/siY2WyTdSMGYpaC79fpzHdu\nXVzo3Wk7/9KOICYWc82y60DuKrlX2UeUzoRP69I9AQKBgQDsecejsmiD3YCu6lul\ngwXXc4r4IfiCaN7KlI4AzbiVE1lUBCDuZXJwfaaXnT15wmHhlq835lLjKkRwTR5x\nlrzotNC5yuwQV9gpUNUq5+3y5XmhPxLoBExAxhEbtOT5+Syp933Zi3RAgqkkw1B2\nxWy0t8rlaTveE0MmsOLlgWEI+wKBgQDI3+fY3N6Sbsp3JiQQJ2SWqd404Ebdfr4o\nBZq0cBsvxOqsT9tIg94BvcZlXHFvBg8vJRNKZywXCWx0giaorv8GXmPprYe7oLEX\n7ErJP6CoXZo5y7KiQvdcywsEbKj8LZDUyEGhBZH4896oEVRHi6PSBN9vp0XBXVGD\ndJpas4ToKQKBgQDrjjtBYrwdbo16r1RvQF6XSS8LELu9G72hyezR/Bp71PRMbnhn\nQIKIb4F80VKlcO2Ti0gqxLGYO0hFHWzP9TlkDIlGKU6Q0RAvx6cvwCwUomVQK8Yn\ne/CBLUtpb/4OyxikjjW8d99rSzw1tKD4TpyEP/hKIVNTWZiwd87skr4X9QKBgGdR\n66G68WxmOhOQ9amtaWqpUtblqO2SnGJfh5RZuVIXuhEJPiQNV6qTnzFRnDLb7gF0\n03hImv/6Y+OFcjb/U8NF16RBEniqjYxdiJX8+TjAdGxX3rjhMvRyp2cOMNkM4trf\nagpVoCBp51ORHkVyiL+kq/x1EEcGJcA0wJP4lFsJAoGBALxToCEw5kyUlnz8R7k3\nKN0YcxBDU1J7RiscPykO63vsf2gb3AFOldNb09hcG5+jph9vUOdOf5zx8gT9EUDg\niCHECGNZl2AZOjglekbs27l2s0U/wMO1MuqwxNP4nzu+CCkp4WFS9zlumL/KtPNp\nMgaMEHy0TMvGuC7OkSudask5\n-----END PRIVATE KEY-----\n",
"client_email": "thors-hammer-electrical@thors-hammer-electrical.iam.gserviceaccount.com",
"client_id": "106578123239401596254",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/thors-hammer-electrical%40thors-hammer-electrical.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}

BIN
static/images/Logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 KiB

BIN
templates/Logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 KiB

131
templates/admin.html Normal file
View File

@@ -0,0 +1,131 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Panel - Work Orders</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
</head>
<body class="bg-dark text-white">
<!-- Admin Panel Header -->
<div class="container mt-5 text-center">
<h2>Thor's Hammer Electrical - Admin Panel</h2>
</div>
<!-- Table Container -->
<div class="container mt-4">
<table class="table table-striped table-bordered table-hover">
<thead class="table-dark">
<tr>
<th>ID</th>
<th>Name</th>
<th>Job</th>
<th>Address</th>
<th>City</th>
<th>State</th>
<th>Zipcode</th>
<th>Phone</th>
<th>Submitted At</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for order in work_orders %}
<tr>
<td>{{ order[0] }}</td> <!-- ID -->
<td>{{ order[1] }}</td> <!-- Name -->
<td>{{ order[2] }}</td> <!-- Job -->
<td>{{ order[3] }}</td> <!-- Address -->
<td>{{ order[4] }}</td> <!-- City -->
<td>{{ order[5] }}</td> <!-- State -->
<td>{{ order[6] }}</td> <!-- Zipcode -->
<td>{{ order[7] }}</td> <!-- Phone -->
<td>{{ order[8] }}</td> <!-- Submitted At -->
<td>
<!-- Delete Button -->
<form action="{{ url_for('delete_order', order_id=order[0]) }}" method="POST" style="display:inline;">
<button type="submit" class="btn btn-danger btn-sm">Delete</button>
</form>
<!-- Mark as Complete Button -->
<form action="{{ url_for('mark_complete', order_id=order[0]) }}" method="POST" style="display:inline;">
<button type="submit" class="btn btn-success btn-sm">Mark as Complete</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Logout Container -->
<div class="container text-center mt-4">
<a href="{{ url_for('completed_jobs') }}" class="btn btn-info btn-lg mb-3">View Completed Jobs</a>
<!-- Logout Button -->
<form action="{{ url_for('logout') }}" method="POST" class="d-inline-block">
<button type="submit" class="btn btn-warning btn-lg mb-3">Logout</button>
</form>
</div>
<h2 class="text-center text-white">Website Analytics</h2>
<div class="row">
<div class="col-md-4">
<div class="card text-center bg-primary text-white">
<div class="card-body">
<h4>Total Visitors</h4>
<h2 id="total-visitors">Loading...</h2>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card text-center bg-success text-white">
<div class="card-body">
<h4>Page Views</h4>
<h2 id="page-views">Loading...</h2>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card text-center bg-warning text-white">
<div class="card-body">
<h4>Most Visited Page</h4>
<h2 id="top-page">Loading...</h2>
</div>
</div>
</div>
</div>
<script>
async function fetchAnalytics() {
try {
let response = await fetch('/analytics');
let data = await response.json();
if (data.error) {
document.getElementById('total-visitors').textContent = "Error";
document.getElementById('page-views').textContent = "Error";
document.getElementById('top-page').textContent = "Error";
} else {
document.getElementById('total-visitors').textContent = data.total_users;
document.getElementById('page-views').textContent = data.total_pageviews;
document.getElementById('top-page').textContent = data.top_page;
}
} catch (error) {
console.error("Error fetching analytics:", error);
}
}
fetchAnalytics();
</script>
<!-- Bootstrap JS and Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
</body>
</html>

View File

@@ -0,0 +1,61 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Panel - Completed Jobs</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
</head>
<body class="bg-dark text-white">
<!-- Admin Panel Header -->
<div class="container mt-5 text-center">
<h2>Thor's Hammer Electrical - Completed Jobs</h2>
</div>
<!-- Table Container -->
<div class="container mt-4">
<table class="table table-striped table-bordered table-hover">
<thead class="table-dark">
<tr>
<th>ID</th>
<th>Name</th>
<th>Job</th>
<th>Address</th>
<th>City</th>
<th>State</th>
<th>Zipcode</th>
<th>Phone</th>
<th>Completed At</th>
</tr>
</thead>
<tbody>
{% for job in completed_jobs %}
<tr>
<td>{{ job[0] }}</td> <!-- ID -->
<td>{{ job[1] }}</td> <!-- Name -->
<td>{{ job[2] }}</td> <!-- Job -->
<td>{{ job[3] }}</td> <!-- Address -->
<td>{{ job[4] }}</td> <!-- City -->
<td>{{ job[5] }}</td> <!-- State -->
<td>{{ job[6] }}</td> <!-- Zipcode -->
<td>{{ job[7] }}</td> <!-- Phone -->
<td>{{ job[8] }}</td> <!-- Completed At -->
</tr>
{% endfor %}
</tbody>
</table>
<div class="back-container">
<a href="{{ url_for('admin') }}">
<button class="btn btn-success btn-sm">
Back to Admin Panel</button>
</a>
</div>
</body>
</html>

74
templates/failure.html Normal file
View File

@@ -0,0 +1,74 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-ZJ4YKQNNG1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-ZJ4YKQNNG1');
</script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Work Order Form</title>
<link rel="stylesheet" href="styles.css" type="text/css" />
<!-- Cyborg Bootstrap Theme -->
<link href="https://cdn.jsdelivr.net/npm/bootswatch@5.3.0/dist/cyborg/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous"></script>
<link rel="icon" href="{{ url_for('static', filename='Logo.png') }}">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Oswald:wght@200..700&display=swap" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<img src="{{ url_for('static', filename='Logo.png') }}" width="60" height="60">
<a class="navbar-brand" href="https://thorshammerelectrical.com/index.html">Thor's Hammer Electrical</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item active">
<a class="nav-link" href="https://thorshammerelectrical.com/About.html">About</a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://thorshammerelectrical.com/Services.html">Services</a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://www.help.thorshammerelectrical.com">Submit Work-Order</a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://thorshammerelectrical.com/Review.html">Reviews</a>
</li>
</ul>
</div>
</nav>
<div class="d-flex min-vh-100 align-items-center justify-content-center">
<div class="d-flex flex-column align-items-center text-center">
<div class="p-2">
<h1>Something went wrong. Please try again!</h1>
</div>
<br>
<div class="p-2">
<button class="btn btn-danger">
<a href="{{ url_for('index') }}" class="text-white text-decoration-none">Back to Form</a>
</button>
</div>
</div>
</div>

113
templates/form.html Normal file
View File

@@ -0,0 +1,113 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-ZJ4YKQNNG1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-ZJ4YKQNNG1');
</script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Work Order Form</title>
<link rel="stylesheet" href="styles.css" type="text/css" />
<!-- Cyborg Bootstrap Theme -->
<link href="https://cdn.jsdelivr.net/npm/bootswatch@5.3.0/dist/cyborg/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous"></script>
<link rel="icon" href="{{ url_for('static', filename='Logo.png') }}">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Oswald:wght@200..700&display=swap" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<img src="{{ url_for('static', filename='Logo.png') }}" width="60" height="60">
<a class="navbar-brand" href="https://thorshammerelectrical.com/index.html">Thor's Hammer Electrical</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item active">
<a class="nav-link" href="https://thorshammerelectrical.com/About.html">About</a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://thorshammerelectrical.com/Services.html">Services</a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://www.help.thorshammerelectrical.com">Submit Work-Order</a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://thorshammerelectrical.com/Review.html">Reviews</a>
</li>
</ul>
</div>
</nav>
<!-- Form Container -->
<div class="col-lg-8 mx-auto">
<h1 class="text-white">Thor's Hammer Electrical Work-Order Form</h1>
<p class="text-white">Got a job for us? Submit it here and Leonard will get back to you as soon as possible!</p>
<form action="/submit" method="POST">
<!-- Form Fields -->
<div class="mb-4">
<label for="name" class="form-label">Name:</label>
<input type="text" id="name" name="name" class="form-control" required>
</div>
<div class="mb-4">
<label for="job" class="form-label">Job Details:</label>
<textarea id="job" name="job" class="form-control" required></textarea>
</div>
<div class="mb-4">
<label for="address" class="form-label">Address:</label>
<textarea id="address" name="address" class="form-control" required></textarea>
</div>
<div class="mb-4">
<label for="city" class="form-label">City:</label>
<input type="text" id="city" name="city" class="form-control" required>
</div>
<div class="mb-4">
<label for="state" class="form-label">State:</label>
<input type="text" id="state" name="state" class="form-control" required>
</div>
<div class="mb-4">
<label for="zipcode" class="form-label">Zip Code:</label>
<input type="text" id="zipcode" name="zipcode" class="form-control" required>
</div>
<div class="mb-4">
<label for="phone" class="form-label">Phone Number:</label>
<input type="text" id="phone" name="phone" class="form-control" required>
</div>
<!-- Submit Button -->
<div class="d-grid gap-2">
<input type="submit" value="Submit" class="btn btn-danger btn-lg">
</div>
</form>
</div>
</div>
<!-- Bootstrap JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
</body>
</html>

67
templates/login.html Normal file
View File

@@ -0,0 +1,67 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
<title>Login</title>
<style>
body {
background: linear-gradient(to right, #667eea, #764ba2);
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.login-container {
background: white;
padding: 2rem;
border-radius: 10px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
text-align: center;
max-width: 400px;
width: 100%;
}
.logo-placeholder {
width: 80px;
height: 80px;
background: #ddd;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
margin: 0 auto 1rem;
font-weight: bold;
color: #666;
}
.logo {
width: 100px; /* Adjust size as needed */
height: auto;
display: block;
margin: 0 auto 1rem; /* Centers the logo */
}
</style>
</head>
<body>
<div class="login-container">
<img src="{{ url_for('static', filename='Logo.png') }}" alt="Logo" class="logo">
<h2 class="mb-3">Login</h2>
<form action="/login" method="POST">
<div class="form-group">
<label for="username">Username</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary btn-block">Login</button>
</form>
</div>
</body>
</html>

9
templates/styles.css Normal file
View File

@@ -0,0 +1,9 @@
.bg-dark-gray {
background-color: #333333; /* Darker Gray */
}
body {
background-color: #000000; /* Black background for body */
}

74
templates/success.html Normal file
View File

@@ -0,0 +1,74 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-ZJ4YKQNNG1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-ZJ4YKQNNG1');
</script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Work Order Form</title>
<link rel="stylesheet" href="styles.css" type="text/css" />
<!-- Cyborg Bootstrap Theme -->
<link href="https://cdn.jsdelivr.net/npm/bootswatch@5.3.0/dist/cyborg/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous"></script>
<link rel="icon" href="{{ url_for('static', filename='Logo.png') }}">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Oswald:wght@200..700&display=swap" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<img src="{{ url_for('static', filename='Logo.png') }}" width="60" height="60">
<a class="navbar-brand" href="https://thorshammerelectrical.com/index.html">Thor's Hammer Electrical</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item active">
<a class="nav-link" href="https://thorshammerelectrical.com/About.html">About</a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://thorshammerelectrical.com/Services.html">Services</a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://www.help.thorshammerelectrical.com">Submit Work-Order</a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://thorshammerelectrical.com/Review.html">Reviews</a>
</li>
</ul>
</div>
</nav>
<div class="d-flex min-vh-100 align-items-center justify-content-center">
<div class="d-flex flex-column align-items-center text-center">
<div class="p-2">
<h1>Success! Thank you for choosing Thor's Hammer</h1>
</div>
<br>
<div class="p-2">
<button class="btn btn-danger">
<a href="https://thorshammerelectrical.com" class="text-white text-decoration-none">Back to Home</a>
</button>
</div>
</div>
</div>

5
wsgi.py Normal file
View File

@@ -0,0 +1,5 @@
from app import app
if __name__ == '__main__':
app.run()